mirror of
https://github.com/schrodinger/pymol-open-source.git
synced 2026-06-04 20:04:21 +08:00
5730 lines
165 KiB
C++
5730 lines
165 KiB
C++
/*
|
|
A* -------------------------------------------------------------------
|
|
B* This file contains source code for the PyMOL computer program
|
|
C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific.
|
|
D* -------------------------------------------------------------------
|
|
E* It is unlawful to modify or remove this copyright notice.
|
|
F* -------------------------------------------------------------------
|
|
G* Please see the accompanying LICENSE file for further information.
|
|
H* -------------------------------------------------------------------
|
|
I* Additional authors of this source file include:
|
|
-* sc
|
|
-*
|
|
-*
|
|
Z* -------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
#include"os_std.h"
|
|
#include"os_gl.h"
|
|
#include"os_python.h"
|
|
#include"os_numpy.h"
|
|
|
|
#include"Util.h"
|
|
#include "pymol/utility.h"
|
|
|
|
#include"Word.h"
|
|
#include"main.h"
|
|
#include"Base.h"
|
|
#include"MemoryDebug.h"
|
|
#include"Err.h"
|
|
#include"Matrix.h"
|
|
#include"ListMacros.h"
|
|
#include"PyMOLObject.h"
|
|
#include"Scene.h"
|
|
#include"SceneRay.h"
|
|
#include"SceneMouse.h"
|
|
#include"ScenePicking.h"
|
|
#include"Ortho.h"
|
|
#include"Vector.h"
|
|
#include"ButMode.h"
|
|
#include"Control.h"
|
|
#include"Selector.h"
|
|
#include"Setting.h"
|
|
#include"Movie.h"
|
|
#include"MyPNG.h"
|
|
#include"P.h"
|
|
#include"Editor.h"
|
|
#include"Executive.h"
|
|
#include"Wizard.h"
|
|
#include"CGO.h"
|
|
#include"ObjectDist.h"
|
|
#include"ObjectGadget.h"
|
|
#include"Seq.h"
|
|
#include"Menu.h"
|
|
#include"View.h"
|
|
#include"ObjectSlice.h"
|
|
#include"Text.h"
|
|
#include"PyMOLOptions.h"
|
|
#include"PyMOL.h"
|
|
#include"PConv.h"
|
|
#include"ScrollBar.h"
|
|
#include "ShaderMgr.h"
|
|
#include "Feedback.h"
|
|
#include "GFXManager.h"
|
|
#include "Util2.h"
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
#include"OpenVRMode.h"
|
|
#endif
|
|
|
|
//#define _OPENVR_STEREO_DEBUG_VIEWS
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <glm/vec3.hpp>
|
|
#include <glm/mat4x4.hpp>
|
|
#include <optional>
|
|
#include <utility>
|
|
|
|
static void glReadBufferError(PyMOLGlobals *G, GLenum b, GLenum e){
|
|
PRINTFB(G, FB_OpenGL, FB_Warnings)
|
|
" WARNING: glReadBuffer caused GL error 0x%04x\n", e ENDFB(G);
|
|
}
|
|
// TH 2013-11-01: glReadBuffer fails in JyMOL when picking, OSX 10.9, Intel Graphics
|
|
// for minor cases (i.e., png is called) this might get called outside of the main
|
|
// thread (from ExecutiveDrawNow()) in this case, just don't call glReadBuffer for
|
|
// now, it should be ok because i believe it is used to figure out the size of the
|
|
// 3D window (using SceneImagePrepareImpl) in situations where the size gets changed.
|
|
#define glReadBuffer(b) { int e; if (PIsGlutThread()) glReadBuffer(b); \
|
|
if((e = glGetError())) glReadBufferError(G, b, e); }
|
|
|
|
#define cSliceMin 1.0F
|
|
|
|
#define SceneLineHeight 127
|
|
#define SceneTopMargin 0
|
|
#define SceneBottomMargin 3
|
|
#define SceneLeftMargin 3
|
|
|
|
/* Shared with ShaderMgr */
|
|
/** Coefficients from: http://3dtv.at/Knowhow/AnaglyphComparison_en.aspx */
|
|
/** Optimize the look and feel of anaglyph 3D */
|
|
/* the last mode is the 3x3 identity */
|
|
// matrices are column major
|
|
float anaglyphL_constants[6][9] = { { 0.299, 0.000, 0.000, 0.587, 0.000, 0.000, 0.114, 0.000, 0.000 },
|
|
{ 0.299, 0.000, 0.000, 0.587, 0.000, 0.000, 0.114, 0.000, 0.000 },
|
|
{ 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000 },
|
|
{ 0.299, 0.000, 0.000, 0.587, 0.000, 0.000, 0.114, 0.000, 0.000 },
|
|
{ 0.000, 0.000, 0.000, 0.700, 0.000, 0.000, 0.300, 0.000, 0.000 },
|
|
{ 1.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 1.000 } };
|
|
|
|
float anaglyphR_constants[6][9] = { { 0.000, 0.000, 0.299, 0.000, 0.000, 0.587, 0.000, 0.000, 0.114 },
|
|
{ 0.000, 0.299, 0.299, 0.000, 0.587, 0.587, 0.000, 0.114, 0.114 },
|
|
{ 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 1.000 },
|
|
{ 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 1.000 },
|
|
{ 0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 1.000 },
|
|
{ 1.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 1.000 } };
|
|
|
|
#define F2UI(a) (unsigned int) ((a) * 255.0)
|
|
|
|
|
|
/* allow up to 10 seconds at 30 FPS */
|
|
|
|
/* EXPERIMENTAL VOLUME RAYTRACING DATA */
|
|
extern float *rayDepthPixels;
|
|
extern int rayVolume, rayWidth, rayHeight;
|
|
|
|
static void SceneRestartPerfTimer(PyMOLGlobals * G);
|
|
#define SceneRotateWithDirty SceneRotate
|
|
static void SceneClipSetWithDirty(PyMOLGlobals * G, float front, float back, int dirty);
|
|
|
|
int SceneViewEqual(SceneViewType left, SceneViewType right)
|
|
{
|
|
int i;
|
|
for(i = 0; i < cSceneViewSize; i++) {
|
|
if(fabs(left[i] - right[i]) > R_SMALL4)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Rect2D GridSetRayViewport(GridInfo& I, int slot)
|
|
{
|
|
Rect2D view{};
|
|
if (slot)
|
|
I.slot = slot + I.first_slot - 1;
|
|
else
|
|
I.slot = slot;
|
|
/* if we are in grid mode, then prepare the grid slot viewport */
|
|
if (slot < 0) {
|
|
return I.cur_view;
|
|
} else if (slot == 0) {
|
|
view.offset = Offset2D{};
|
|
view.extent.width = static_cast<std::uint32_t>(I.cur_view.extent.width / I.n_col);
|
|
view.extent.height = static_cast<std::uint32_t>(I.cur_view.extent.height / I.n_row);
|
|
if (I.n_col < I.n_row) {
|
|
view.extent.width *= I.n_col;
|
|
view.extent.height *= I.n_col;
|
|
} else {
|
|
view.extent.width *= I.n_row;
|
|
view.extent.height *= I.n_row;
|
|
}
|
|
view.offset.x += I.cur_view.offset.x + (I.cur_view.extent.width - view.extent.width) / 2;
|
|
view.offset.y += I.cur_view.offset.y;
|
|
} else {
|
|
int abs_grid_slot = slot - I.first_slot;
|
|
int grid_col = abs_grid_slot % I.n_col;
|
|
int grid_row = (abs_grid_slot / I.n_col);
|
|
view.offset.x =
|
|
static_cast<std::int32_t>((grid_col * I.cur_view.extent.width) / I.n_col);
|
|
view.offset.y = static_cast<std::int32_t>(
|
|
I.cur_view.extent.height - ((grid_row + 1) * I.cur_view.extent.height) / I.n_row);
|
|
view.extent.width = ((grid_col + 1) * I.cur_view.extent.width) / I.n_col - view.offset.x;
|
|
view.extent.height = static_cast<std::uint32_t>(
|
|
(I.cur_view.extent.height - ((grid_row) *I.cur_view.extent.height) / I.n_row) - view.offset.y);
|
|
view.offset.x += I.cur_view.offset.x;
|
|
view.offset.y += I.cur_view.offset.y;
|
|
}
|
|
return view;
|
|
}
|
|
|
|
void GridUpdate(GridInfo * I, float asp_ratio, GridMode mode, int size)
|
|
{
|
|
if (mode != GridMode::NoGrid) {
|
|
I->size = size;
|
|
I->mode = mode;
|
|
{
|
|
int n_row = 1;
|
|
int n_col = 1;
|
|
int r_size = size;
|
|
while((n_row * n_col) < r_size) {
|
|
float asp1 = asp_ratio * (n_row + 1.0) / n_col;
|
|
float asp2 = asp_ratio * (n_row) / (n_col + 1.0);
|
|
if(asp1 < 1.0F)
|
|
asp1 = 1.0 / asp1;
|
|
if(asp2 < 1.0F)
|
|
asp2 = 1.0 / asp2;
|
|
if(fabs(asp1) > fabs(asp2))
|
|
n_col++;
|
|
else
|
|
n_row++;
|
|
}
|
|
|
|
// the above algorithm can generate a 3x2 grid for size=4, but we want a
|
|
// 2x2 in that case.
|
|
while ((n_col - 1) * n_row >= size && size) {
|
|
n_col -= 1;
|
|
}
|
|
while ((n_row - 1) * n_col >= size && size) {
|
|
n_row -= 1;
|
|
}
|
|
|
|
I->n_row = n_row;
|
|
I->n_col = n_col;
|
|
}
|
|
if(I->size > 1) {
|
|
I->active = true;
|
|
I->asp_adjust = (float) I->n_row / I->n_col;
|
|
I->first_slot = 1;
|
|
I->last_slot = I->size;
|
|
} else {
|
|
I->active = false;
|
|
}
|
|
} else {
|
|
I->active = false;
|
|
}
|
|
}
|
|
|
|
void SceneInvalidateStencil(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->StencilValid = false;
|
|
}
|
|
|
|
int SceneGetGridSize(PyMOLGlobals* G, GridMode grid_mode)
|
|
{
|
|
CScene* I = G->Scene;
|
|
int size = 0;
|
|
|
|
switch (grid_mode) {
|
|
case GridMode::ByObject:
|
|
if (I->m_slots.empty()) {
|
|
I->m_slots.push_back(0);
|
|
} else {
|
|
std::fill(I->m_slots.begin(), I->m_slots.end(), 0);
|
|
}
|
|
{
|
|
int max_slot = 0;
|
|
for (auto& obj : I->Obj) {
|
|
if (auto slot = obj->grid_slot) {
|
|
max_slot = std::max(slot, max_slot);
|
|
if (slot > 0) {
|
|
VecCheck(I->m_slots, slot);
|
|
I->m_slots[slot] = 1;
|
|
}
|
|
}
|
|
}
|
|
for (int slot = 0; slot <= max_slot; slot++) {
|
|
if (I->m_slots[slot])
|
|
I->m_slots[slot] = ++size;
|
|
}
|
|
}
|
|
break;
|
|
case GridMode::ByObjectStates:
|
|
case GridMode::ByObjectByState:
|
|
if (!I->m_slots.empty()) {
|
|
I->m_slots.clear();
|
|
}
|
|
{
|
|
int max_slot = 0;
|
|
for (auto& obj : I->Obj) {
|
|
auto slot = obj->getNFrame();
|
|
if (grid_mode == GridMode::ByObjectByState) {
|
|
obj->grid_slot = max_slot; // slot offset for 1st state
|
|
max_slot += slot;
|
|
} else if (max_slot < slot) {
|
|
max_slot = slot;
|
|
}
|
|
}
|
|
size = max_slot;
|
|
}
|
|
break;
|
|
}
|
|
auto grid_max = SettingGet<int>(G, cSetting_grid_max);
|
|
if (grid_max >= 0)
|
|
size = std::min(size, grid_max);
|
|
return size;
|
|
}
|
|
|
|
int SceneHasImage(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
return (I->Image && !I->Image->empty());
|
|
}
|
|
|
|
int SceneMustDrawBoth(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
return (G->StereoCapable &&
|
|
((I->StereoMode == 1) ||
|
|
SettingGetGlobal_b(G, cSetting_stereo_double_pump_mono)));
|
|
}
|
|
|
|
static int SceneDeferClickWhen(Block * block, int button, int x, int y, double when,
|
|
int mod);
|
|
|
|
int stereo_via_adjacent_array(int stereo_mode)
|
|
{
|
|
switch (stereo_mode) {
|
|
case cStereo_crosseye:
|
|
case cStereo_walleye:
|
|
case cStereo_sidebyside:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int StereoIsAdjacent(PyMOLGlobals * G){
|
|
CScene *I = G->Scene;
|
|
return stereo_via_adjacent_array(I->StereoMode);
|
|
}
|
|
|
|
void SceneAbortAnimation(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if(I->cur_ani_elem < I->n_ani_elem) { /* allow user to override animation */
|
|
I->cur_ani_elem = I->n_ani_elem;
|
|
}
|
|
}
|
|
|
|
void ScenePrimeAnimation(PyMOLGlobals * G)
|
|
{
|
|
if(G->HaveGUI) {
|
|
CScene *I = G->Scene;
|
|
UtilZeroMem(I->ani_elem, sizeof(CViewElem));
|
|
SceneToViewElem(G, I->ani_elem, nullptr);
|
|
I->ani_elem[0].specification_level = 2;
|
|
I->n_ani_elem = 0;
|
|
}
|
|
}
|
|
|
|
static float SceneGetFPS(PyMOLGlobals * G)
|
|
{
|
|
float fps = SettingGetGlobal_f(G, cSetting_movie_fps);
|
|
float minTime;
|
|
if(fps <= 0.0F) {
|
|
if(fps < 0.0)
|
|
minTime = 0.0; /* negative fps means full speed */
|
|
else /* 0 fps means use movie_delay instead */
|
|
minTime = SettingGetGlobal_f(G, cSetting_movie_delay) / 1000.0;
|
|
if(minTime >= 0.0F)
|
|
fps = 1.0F / minTime;
|
|
else
|
|
fps = 1000.0F;
|
|
}
|
|
return fps;
|
|
}
|
|
|
|
/**
|
|
* Release `G->Scene->Image` and clear related flags (`CopyType`).
|
|
*
|
|
* Invalidates the Ortho CGO and requests a redisplay. It's not entirely clear
|
|
* why this is done, a code comment says "need to invalidate since text could be
|
|
* shown".
|
|
*/
|
|
static void ScenePurgeImage(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->CopyType = false;
|
|
I->Image = nullptr;
|
|
|
|
// TODO does this belong here?
|
|
if (true /* !noinvalid */)
|
|
OrthoInvalidateDoDraw(G); // right now, need to invalidate since text could be shown
|
|
}
|
|
|
|
void SceneInvalidateCopy(PyMOLGlobals * G, int free_buffer)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if(I) {
|
|
if(free_buffer){
|
|
ScenePurgeImage(G);
|
|
}
|
|
else{
|
|
I->Image = nullptr;
|
|
}
|
|
if (I->CopyType)
|
|
OrthoInvalidateDoDraw(G); // right now, need to invalidate since text could be shown
|
|
I->CopyType = false;
|
|
}
|
|
}
|
|
|
|
void SceneInvalidate(PyMOLGlobals * G)
|
|
{
|
|
SceneInvalidateCopy(G, false);
|
|
SceneDirty(G);
|
|
PyMOL_NeedRedisplay(G->PyMOL);
|
|
}
|
|
|
|
void SceneLoadAnimation(PyMOLGlobals * G, double duration, int hand)
|
|
{
|
|
if(G->HaveGUI) {
|
|
double now;
|
|
int target = (int) (duration * 30);
|
|
CScene *I = G->Scene;
|
|
if(target < 1)
|
|
target = 1;
|
|
if(target > MAX_ANI_ELEM)
|
|
target = MAX_ANI_ELEM;
|
|
UtilZeroMem(I->ani_elem + 1, sizeof(CViewElem) * target);
|
|
SceneToViewElem(G, I->ani_elem + target, nullptr);
|
|
I->ani_elem[target].specification_level = 2;
|
|
now = UtilGetSeconds(G);
|
|
I->ani_elem[0].timing_flag = true;
|
|
I->ani_elem[0].timing = now + 0.01;
|
|
I->ani_elem[target].timing_flag = true;
|
|
I->ani_elem[target].timing = now + duration;
|
|
ViewElemInterpolate(G, I->ani_elem, I->ani_elem + target,
|
|
2.0F, 1.0F, true, 0.0F, hand, 0.0F);
|
|
SceneFromViewElem(G, I->ani_elem, true);
|
|
I->cur_ani_elem = 0;
|
|
I->n_ani_elem = target;
|
|
I->AnimationStartTime = UtilGetSeconds(G);
|
|
I->AnimationStartFlag = true;
|
|
I->AnimationStartFrame = SceneGetFrame(G);
|
|
I->AnimationLagTime = 0.0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set FrontSafe and BackSafe
|
|
*/
|
|
void UpdateFrontBackSafe(CScene *I)
|
|
{
|
|
float front = I->m_view.m_clip().m_front;
|
|
float back = I->m_view.m_clip().m_back;
|
|
|
|
// minimum slab
|
|
if(back - front < cSliceMin) {
|
|
float avg = (back + front) / 2.0;
|
|
back = avg + cSliceMin / 2.0;
|
|
front = avg - cSliceMin / 2.0;
|
|
}
|
|
|
|
// minimum front
|
|
if (front < cSliceMin) {
|
|
front = cSliceMin;
|
|
|
|
// minimum slab
|
|
if (back < front + cSliceMin)
|
|
back = front + cSliceMin;
|
|
}
|
|
|
|
I->m_view.m_clipSafe().m_front = front;
|
|
I->m_view.m_clipSafe().m_back = back;
|
|
}
|
|
|
|
#define SELE_MODE_MAX 7
|
|
|
|
static const char SelModeKW[][20] = {
|
|
"",
|
|
"byresi",
|
|
"bychain",
|
|
"bysegi",
|
|
"byobject",
|
|
"bymol",
|
|
"bca.",
|
|
};
|
|
|
|
static void SceneUpdateInvMatrix(PyMOLGlobals * G)
|
|
{
|
|
// TODO: Test glm::inverse(I->m_view.rotMatrix())
|
|
CScene *I = G->Scene;
|
|
const auto rm = glm::value_ptr(I->m_view.rotMatrix());
|
|
float *im = I->InvMatrix;
|
|
im[0] = rm[0];
|
|
im[1] = rm[4];
|
|
im[2] = rm[8];
|
|
im[3] = 0.0F;
|
|
im[4] = rm[1];
|
|
im[5] = rm[5];
|
|
im[6] = rm[9];
|
|
im[7] = 0.0F;
|
|
im[8] = rm[2];
|
|
im[9] = rm[6];
|
|
im[10] = rm[10];
|
|
im[11] = 0.0F;
|
|
im[12] = 0.0F;
|
|
im[13] = 0.0F;
|
|
im[14] = 0.0F;
|
|
im[15] = 1.0F;
|
|
}
|
|
|
|
void SceneUpdateStereo(PyMOLGlobals * G)
|
|
{
|
|
SceneSetStereo(G, SettingGetGlobal_b(G, cSetting_stereo));
|
|
PyMOL_NeedRedisplay(G->PyMOL);
|
|
}
|
|
|
|
/**
|
|
* Get the selection operator for the `mouse_selection_mode` setting.
|
|
* Example: `mouse_selection_mode=2` -> "bychain"
|
|
*/
|
|
const char *SceneGetSeleModeKeyword(PyMOLGlobals * G)
|
|
{
|
|
int sel_mode = SettingGetGlobal_i(G, cSetting_mouse_selection_mode);
|
|
if((sel_mode >= 0) && (sel_mode < SELE_MODE_MAX))
|
|
return (char *) SelModeKW[sel_mode];
|
|
return (char *) SelModeKW[0];
|
|
}
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
static float s_oldFov = -1.0f;
|
|
#endif
|
|
|
|
void SceneToViewElem(PyMOLGlobals * G, CViewElem * elem, const char *scene_name)
|
|
{
|
|
double *dp;
|
|
CScene *I = G->Scene;
|
|
const auto& pos = I->m_view.pos();
|
|
const auto& ori = I->m_view.origin();
|
|
|
|
float dY = 0, dZ = 0;
|
|
float fov = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
float scale = 1.0f / I->Scale;
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
if (I->StereoMode == cStereo_openvr) {
|
|
float dist = fabsf(pos.z);
|
|
float fovVR = fov;
|
|
fov = s_oldFov;
|
|
dY = scale * 1.0f;
|
|
dZ = scale * dist * (tanf(fovVR * PI / 360.f) / tanf(fov * PI / 360.f) - 1.0f);
|
|
}
|
|
#endif
|
|
|
|
/* copy rotation matrix */
|
|
elem->matrix_flag = true;
|
|
dp = elem->matrix;
|
|
auto fp = glm::value_ptr(I->m_view.rotMatrix());
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
*(dp++) = (double) *(fp++);
|
|
|
|
*(dp++) = 0.0F;
|
|
*(dp++) = 0.0F;
|
|
*(dp++) = 0.0F;
|
|
*(dp++) = 1.0F;
|
|
|
|
/* copy position */
|
|
elem->pre_flag = true;
|
|
dp = elem->pre;
|
|
*(dp++) = (double) pos.x * scale;
|
|
*(dp++) = (double) pos.y * scale - dY;
|
|
*(dp++) = (double) pos.z * scale - dZ;
|
|
|
|
/* copy origin (negative) */
|
|
elem->post_flag = true;
|
|
dp = elem->post;
|
|
*(dp++) = (double) -ori.x;
|
|
*(dp++) = (double) -ori.y;
|
|
*(dp++) = (double) -ori.z;
|
|
|
|
elem->clip_flag = true;
|
|
elem->front = I->m_view.m_clip().m_front * scale + dZ;
|
|
elem->back = I->m_view.m_clip().m_back * scale + dZ;
|
|
|
|
elem->ortho_flag = true;
|
|
elem->ortho = SettingGetGlobal_b(G, cSetting_ortho) ? fov : -fov;
|
|
|
|
{
|
|
if(elem->scene_flag && elem->scene_name) {
|
|
OVLexicon_DecRef(G->Lexicon, elem->scene_name);
|
|
elem->scene_name = 0;
|
|
elem->scene_flag = 0;
|
|
}
|
|
}
|
|
{
|
|
if(!scene_name)
|
|
scene_name = SettingGetGlobal_s(G, cSetting_scene_current_name);
|
|
if(scene_name && scene_name[0]) {
|
|
OVreturn_word result = OVLexicon_GetFromCString(G->Lexicon, scene_name);
|
|
if(OVreturn_IS_OK(result)) {
|
|
elem->scene_name = result.word;
|
|
elem->scene_flag = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf, IV: %11lf ==> EP: %11lf %11lf %11lf, EF/EB: %11lf %11lf, EV: %11lf\n",
|
|
"SceneToViewElem",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale,
|
|
SettingGetGlobal_f(G, cSetting_field_of_view),
|
|
elem->pre[0], elem->pre[1], elem->pre[2],
|
|
elem->front, elem->back,
|
|
elem->ortho
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
}
|
|
|
|
void SceneFromViewElem(PyMOLGlobals * G, CViewElem * elem, int dirty)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float *fp;
|
|
double *dp;
|
|
int changed_flag = false;
|
|
|
|
float dY = 0, dZ = 0;
|
|
float fov = elem->ortho;
|
|
float scale = I->Scale;
|
|
auto pos = I->m_view.pos();
|
|
auto ori = I->m_view.origin();
|
|
auto rot = I->m_view.rotMatrix();
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
if (I->StereoMode == cStereo_openvr) {
|
|
float dist = fabsf(elem->pre[2]);
|
|
float fovVR = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
dY = -1.0f;
|
|
dZ = scale * dist * (tanf(fabsf(fov) * PI / 360.f) / tanf(fovVR * PI / 360.f) - 1.0f);
|
|
fov = fov < 0.0f ? -fovVR : fovVR;
|
|
}
|
|
#endif
|
|
|
|
if(elem->matrix_flag) {
|
|
rot = glm::make_mat4(elem->matrix);
|
|
changed_flag = true;
|
|
SceneUpdateInvMatrix(G);
|
|
}
|
|
|
|
if(elem->pre_flag) {
|
|
dp = elem->pre;
|
|
fp = glm::value_ptr(pos);
|
|
*(fp++) = (float) *(dp++) * scale;
|
|
*(fp++) = (float) *(dp++) * scale - dY;
|
|
*(fp++) = (float) *(dp++) * scale - dZ;
|
|
changed_flag = true;
|
|
}
|
|
|
|
if(elem->post_flag) {
|
|
dp = elem->post;
|
|
fp = glm::value_ptr(ori);
|
|
*(fp++) = (float) (-*(dp++));
|
|
*(fp++) = (float) (-*(dp++));
|
|
*(fp++) = (float) (-*(dp++));
|
|
changed_flag = true;
|
|
}
|
|
|
|
if(elem->clip_flag) {
|
|
SceneClipSetWithDirty(G, elem->front * scale + dZ, elem->back * scale + dZ, dirty);
|
|
}
|
|
|
|
if(elem->ortho_flag) {
|
|
if(fov < 0.0F) {
|
|
SettingSetGlobal_b(G, cSetting_ortho, 0);
|
|
if(fov < -(1.0F - R_SMALL4)) {
|
|
SettingSetGlobal_f(G, cSetting_field_of_view, -fov);
|
|
}
|
|
} else {
|
|
SettingSetGlobal_b(G, cSetting_ortho, (fov > 0.5F));
|
|
if(fov > (1.0F + R_SMALL4)) {
|
|
SettingSetGlobal_f(G, cSetting_field_of_view, fov);
|
|
}
|
|
}
|
|
}
|
|
if(elem->state_flag&&!MovieDefined(G)) {
|
|
SettingSetGlobal_i(G, cSetting_state, (elem->state)+1);
|
|
}
|
|
if(changed_flag) {
|
|
SceneRestartSweepTimer(G);
|
|
I->RockFrame = 0;
|
|
SceneRovingDirty(G);
|
|
I->m_view.setPos(pos);
|
|
I->m_view.setOrigin(ori);
|
|
I->m_view.setRotMatrix(rot);
|
|
}
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf, IV: %11lf <== EP: %11lf %11lf %11lf, EF/EB: %11lf %11lf, EV: %11lf\n",
|
|
"SceneFromViewElem",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale,
|
|
SettingGetGlobal_f(G, cSetting_field_of_view),
|
|
elem->pre[0], elem->pre[1], elem->pre[2],
|
|
elem->front, elem->back,
|
|
elem->ortho
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
}
|
|
|
|
SceneUnitContext ScenePrepareUnitContext(const Extent2D& extent)
|
|
{
|
|
SceneUnitContext context{};
|
|
float tw = 1.0F;
|
|
float th = 1.0F;
|
|
float aspRat = extent.height != 0
|
|
? (extent.width / static_cast<float>(extent.height))
|
|
: 1.0f;
|
|
|
|
if(aspRat > 1.0F) {
|
|
tw = aspRat;
|
|
} else {
|
|
th = 1.0F / aspRat;
|
|
}
|
|
|
|
context.unit_left = (1.0F - tw) / 2;
|
|
context.unit_right = (tw + 1.0F) / 2;
|
|
context.unit_top = (1.0F - th) / 2;
|
|
context.unit_bottom = (th + 1.0F) / 2;
|
|
context.unit_front = -0.5F;
|
|
context.unit_back = 0.5F;
|
|
/*
|
|
printf(
|
|
"ScenePrepareUnitContext:%8.3f %8.3f %8.3f %8.3f %8.3f %8.3f\n",
|
|
context->unit_left,
|
|
context->unit_right,
|
|
context->unit_top,
|
|
context->unit_bottom,
|
|
context->unit_front,
|
|
context->unit_back);
|
|
*/
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* cmd.get_viewport()
|
|
*/
|
|
void SceneGetWidthHeight(PyMOLGlobals * G, int *width, int *height)
|
|
{
|
|
CScene *I = G->Scene;
|
|
*width = I->Width;
|
|
*height = I->Height;
|
|
}
|
|
|
|
Extent2D SceneGetExtent(PyMOLGlobals* G)
|
|
{
|
|
return Extent2D{static_cast<std::uint32_t>(G->Scene->Width),
|
|
static_cast<std::uint32_t>(G->Scene->Height)};
|
|
}
|
|
|
|
void SceneSetExtent(PyMOLGlobals* G, const Extent2D& extent)
|
|
{
|
|
G->Scene->Width = extent.width;
|
|
G->Scene->Height = extent.height;
|
|
}
|
|
|
|
Rect2D SceneGetRect(PyMOLGlobals* G)
|
|
{
|
|
auto I = G->Scene;
|
|
return Rect2D{Offset2D{I->rect.left, I->rect.bottom}, SceneGetExtent(G)};
|
|
}
|
|
|
|
float SceneGetAspectRatio(PyMOLGlobals* G)
|
|
{
|
|
auto extent = SceneGetExtent(G);
|
|
return static_cast<float>(extent.width) / static_cast<float>(extent.height);
|
|
}
|
|
|
|
/**
|
|
* Get the actual current (sub-)viewport size, considering grid mode and
|
|
* side-by-side stereo
|
|
*/
|
|
Extent2D SceneGetExtentStereo(PyMOLGlobals* G)
|
|
{
|
|
auto I = G->Scene;
|
|
|
|
if (I->grid.active) {
|
|
// TODO: this considers "draw W, H" (PYMOL-2775)
|
|
return I->grid.cur_viewport_size;
|
|
}
|
|
|
|
Extent2D extent{static_cast<std::uint32_t>(I->Width),
|
|
static_cast<std::uint32_t>(I->Height)};
|
|
// TODO: this does NOT consider "draw W, H" (PYMOL-2775)
|
|
if (stereo_via_adjacent_array(I->StereoMode))
|
|
extent.width /= 2.f;
|
|
return extent;
|
|
}
|
|
|
|
void SceneSetCardInfo(PyMOLGlobals * G,
|
|
const char *vendor,
|
|
const char *renderer,
|
|
const char *version)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if (!vendor) vendor = "(null)";
|
|
if (!renderer) renderer = "(null)";
|
|
if (!version) version = "(null)";
|
|
UtilNCopy(I->vendor, vendor, sizeof(OrthoLineType) - 1);
|
|
UtilNCopy(I->renderer, renderer, sizeof(OrthoLineType) - 1);
|
|
UtilNCopy(I->version, version, sizeof(OrthoLineType) - 1);
|
|
}
|
|
|
|
int SceneGetStereo(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
return (I->StereoMode);
|
|
}
|
|
|
|
void SceneGetCardInfo(PyMOLGlobals * G, char **vendor, char **renderer, char **version)
|
|
{
|
|
CScene *I = G->Scene;
|
|
(*vendor) = I->vendor;
|
|
(*renderer) = I->renderer;
|
|
(*version) = I->version;
|
|
}
|
|
|
|
void SceneSuppressMovieFrame(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->MovieFrameFlag = false;
|
|
}
|
|
|
|
/**
|
|
* Get center of screen in world coordinates
|
|
*
|
|
* @param[out] pos 3f output vector
|
|
*/
|
|
void SceneGetCenter(PyMOLGlobals * G, float *pos)
|
|
{
|
|
CScene *I = G->Scene;
|
|
auto camPos = I->m_view.pos();
|
|
const auto& ori = I->m_view.origin();
|
|
|
|
MatrixTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), glm::value_ptr(ori), pos);
|
|
|
|
pos[0] -= camPos.x;
|
|
pos[1] -= camPos.y;
|
|
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), pos, pos);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
int SceneGetNFrame(PyMOLGlobals * G, int *has_movie)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if(has_movie)
|
|
*has_movie = I->HasMovie;
|
|
return (I->NFrame);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
Get information required to define the geometry
|
|
of a particular view, for shipping to and from python
|
|
as a list of floats
|
|
@verbatim
|
|
0-15 = 4x4 rotation matrix
|
|
16-18 = position
|
|
19-21 = origin
|
|
22 = front plane
|
|
23 = rear plane
|
|
24 = orthoscopic flag
|
|
@endverbatim
|
|
@param[out] view buffer to fill
|
|
*/
|
|
void SceneGetView(PyMOLGlobals * G, SceneViewType view)
|
|
{
|
|
float *p;
|
|
CScene *I = G->Scene;
|
|
p = view;
|
|
|
|
float dY = 0, dZ = 0;
|
|
float fov = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
float scale = 1.0f / I->Scale;
|
|
const auto& pos = I->m_view.pos();
|
|
const auto& ori = I->m_view.origin();
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
if (I->StereoMode == cStereo_openvr) {
|
|
float dist = fabsf(pos.z);
|
|
float fovVR = fov;
|
|
fov = s_oldFov;
|
|
dY = scale * 1.0f;
|
|
dZ = scale * dist * (tanf(fovVR * PI / 360.f) / tanf(fov * PI / 360.f) - 1.0f);
|
|
}
|
|
#endif
|
|
|
|
std::copy_n(glm::value_ptr(I->m_view.rotMatrix()), 16, p);
|
|
p += 16;
|
|
*(p++) = pos.x * scale;
|
|
*(p++) = pos.y * scale - dY;
|
|
*(p++) = pos.z * scale - dZ;
|
|
*(p++) = ori.x;
|
|
*(p++) = ori.y;
|
|
*(p++) = ori.z;
|
|
*(p++) = I->m_view.m_clip().m_front * scale + dZ;
|
|
*(p++) = I->m_view.m_clip().m_back * scale + dZ;
|
|
*(p++) = SettingGetGlobal_b(G, cSetting_ortho) ? fov : -fov;
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf, IV: %11lf ==> EP: %11lf %11lf %11lf, EF/EB: %11lf %11lf, EV: %11lf\n",
|
|
"SceneGetView",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale,
|
|
SettingGetGlobal_f(G, cSetting_field_of_view),
|
|
view[16], view[17], view[18],
|
|
view[22], view[23],
|
|
view[24]
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneSetView(PyMOLGlobals * G, const SceneViewType view,
|
|
int quiet, float animate, int hand)
|
|
{
|
|
const float *p;
|
|
CScene *I = G->Scene;
|
|
|
|
if(animate < 0.0F) {
|
|
if(SettingGetGlobal_b(G, cSetting_animation))
|
|
animate = SettingGetGlobal_f(G, cSetting_animation_duration);
|
|
else
|
|
animate = 0.0F;
|
|
}
|
|
if(animate != 0.0F)
|
|
ScenePrimeAnimation(G);
|
|
else {
|
|
SceneAbortAnimation(G);
|
|
}
|
|
|
|
float dY = 0, dZ = 0;
|
|
float fov = view[24];
|
|
float scale = I->Scale;
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
if (I->StereoMode == cStereo_openvr) {
|
|
float dist = fabsf(view[18]);
|
|
float fovVR = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
dY = -1.0f;
|
|
dZ = scale * dist * (tanf(fabsf(fov) * PI / 360.f) / tanf(fovVR * PI / 360.f) - 1.0f);
|
|
fov = fov < 0.0f ? -fovVR : fovVR;
|
|
}
|
|
#endif
|
|
|
|
p = view;
|
|
I->m_view.setRotMatrix(glm::make_mat4(p));
|
|
p += 16;
|
|
SceneUpdateInvMatrix(G);
|
|
auto newX = *(p++) * scale;
|
|
auto newY = *(p++) * scale - dY;
|
|
auto newZ = *(p++) * scale - dZ;
|
|
I->m_view.setPos(newX, newY, newZ);
|
|
auto newOriX = *(p++);
|
|
auto newOriY = *(p++);
|
|
auto newOriZ = *(p++);
|
|
I->m_view.setOrigin(newOriX, newOriY, newOriZ);
|
|
|
|
I->LastSweep = 0.0F;
|
|
I->LastSweepX = 0.0F;
|
|
I->LastSweepY = 0.0F;
|
|
I->SweepTime = 0.0;
|
|
I->RockFrame = 0;
|
|
|
|
SceneClipSet(G, p[0] * scale + dZ, p[1] * scale + dZ);
|
|
|
|
p += 2;
|
|
if(fov < 0.0F) {
|
|
SettingSetGlobal_b(G, cSetting_ortho, 0);
|
|
if(fov < -(1.0F - R_SMALL4)) {
|
|
SettingSetGlobal_f(G, cSetting_field_of_view, -fov);
|
|
}
|
|
} else {
|
|
SettingSetGlobal_b(G, cSetting_ortho, (fov > 0.5F));
|
|
if(fov > (1.0F + R_SMALL4)) {
|
|
SettingSetGlobal_f(G, cSetting_field_of_view, fov);
|
|
}
|
|
}
|
|
if(!quiet) {
|
|
PRINTFB(G, FB_Scene, FB_Actions)
|
|
" Scene: view updated.\n" ENDFB(G);
|
|
}
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
const auto& pos = I->m_view.pos();
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf, IV: %11lf <== EP: %11lf %11lf %11lf, EF/EB: %11lf %11lf, EV: %11lf\n",
|
|
"SceneSetView",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale,
|
|
SettingGetGlobal_f(G, cSetting_field_of_view),
|
|
view[16], view[17], view[18],
|
|
view[22], view[23],
|
|
view[24]
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
|
|
if(animate != 0.0F)
|
|
SceneLoadAnimation(G, animate, hand);
|
|
|
|
SceneRovingDirty(G);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneDontCopyNext(PyMOLGlobals * G)
|
|
|
|
/* disables automatic copying of the image for the next rendering run */
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->CopyNextFlag = false;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneUpdateStereoMode(PyMOLGlobals * G)
|
|
{
|
|
if(G->Scene->StereoMode) {
|
|
SceneSetStereo(G, true);
|
|
}
|
|
}
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
/*========================================================================*/
|
|
static
|
|
void ResetFovWidth(PyMOLGlobals * G, bool enableOpenVR, float fovNew) {
|
|
CScene *I = G->Scene;
|
|
auto pos = I->m_view.pos();
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf ==>\n",
|
|
"ResetFovWidth BEFORE",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
|
|
float fovOld = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
SettingSetGlobal_f(G, cSetting_field_of_view, fovNew);
|
|
|
|
const float tanOld = tanf(fovOld * PI / 360.f);
|
|
const float tanNew = tanf(fovNew * PI / 360.f);
|
|
|
|
const float distOld = fabsf(pos.z);
|
|
|
|
const float scaleOld = I->Scale;
|
|
const float scaleNew = I->Scale = enableOpenVR ? 1.0f / (distOld * tanOld) : 1.0f;
|
|
const float scale = scaleNew / scaleOld;
|
|
|
|
auto newX = pos.x * scale;
|
|
auto newY = enableOpenVR ? pos.y * scale + 1.0f : (pos.y - 1.0f) * scale;
|
|
auto newZ = pos.z * scale * tanOld / tanNew;
|
|
|
|
const float dZ = fabsf(pos.z) - distOld * scale;
|
|
I->m_view.m_clip().m_front = I->m_view.m_clip().m_front * scale + dZ;
|
|
I->m_view.m_clip().m_back = I->m_view.m_clip().m_back * scale + dZ;
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf <==\n",
|
|
"ResetFovWidth AFTER",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
I->m_view.setPos(pos);
|
|
UpdateFrontBackSafe(I);
|
|
SceneRovingDirty(G);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
static
|
|
void SceneResetOpenVRSettings(PyMOLGlobals * G, bool enableOpenVR) {
|
|
// set FOV = 110 for openVR
|
|
float openVRFov = 110.0 * 0.5;
|
|
|
|
// old camera props
|
|
static bool commonCorrect = false;
|
|
if (s_oldFov < 0.0f)
|
|
s_oldFov = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
|
|
if (enableOpenVR) {
|
|
s_oldFov = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
ResetFovWidth(G, enableOpenVR, openVRFov);
|
|
commonCorrect = true;
|
|
SettingSetGlobal_f(G, cSetting_dynamic_width_factor, 0.004f); // for correct line width in lines mode
|
|
} else if (commonCorrect){
|
|
ResetFovWidth(G, enableOpenVR, s_oldFov);
|
|
commonCorrect = false;
|
|
SettingSetGlobal_f(G, cSetting_dynamic_width_factor, 0.06f);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*========================================================================*/
|
|
void SceneSetStereo(PyMOLGlobals * G, bool flag)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int cur_stereo_mode = I->StereoMode;
|
|
|
|
if(flag) {
|
|
I->StereoMode = SettingGetGlobal_i(G, cSetting_stereo_mode);
|
|
} else {
|
|
I->StereoMode = 0;
|
|
}
|
|
|
|
SettingSetGlobal_b(G, cSetting_stereo, flag);
|
|
|
|
if (cur_stereo_mode != I->StereoMode) {
|
|
if (cur_stereo_mode == cStereo_geowall ||
|
|
I->StereoMode == cStereo_geowall) {
|
|
OrthoReshape(G, G->Option->winX, G->Option->winY, true);
|
|
}
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
// enter or leave OpenVR mode
|
|
if (I->StereoMode == cStereo_openvr || cur_stereo_mode == cStereo_openvr) {
|
|
bool enableOpenVR = I->StereoMode == cStereo_openvr;
|
|
|
|
// reset camera position
|
|
SceneResetOpenVRSettings(G, enableOpenVR);
|
|
|
|
// force open internal menu
|
|
PParse(G, "cmd.set_wizard_stack()");
|
|
if (enableOpenVR) {
|
|
PParse(G, "wizard openvr");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SceneInvalidateStencil(G);
|
|
SceneInvalidate(G);
|
|
G->ShaderMgr->Set_Reload_Bits(RELOAD_VARIABLES);
|
|
}
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneTranslate(PyMOLGlobals * G, float x, float y, float z)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->m_view.translate(x, y, z);
|
|
SceneClipSet(G, I->m_view.m_clip().m_front - z, I->m_view.m_clip().m_back - z);
|
|
}
|
|
|
|
void SceneTranslateScaled(PyMOLGlobals * G, float x, float y, float z, int sdof_mode)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int invalidate = false;
|
|
|
|
switch (sdof_mode) {
|
|
case SDOF_NORMAL_MODE:
|
|
if((x != 0.0F) || (y != 0.0F)) {
|
|
float vScale = SceneGetExactScreenVertexScale(G, nullptr);
|
|
float factor = vScale * (I->Height + I->Width) / 2;
|
|
I->m_view.translate(x * factor, y * factor, 0.0f);
|
|
invalidate = true;
|
|
}
|
|
if(z != 0.0F) {
|
|
float factor = ((I->m_view.m_clipSafe().m_front + I->m_view.m_clipSafe().m_back) / 2); /* average distance within visible space */
|
|
if(factor > 0.0F) {
|
|
factor *= z;
|
|
I->m_view.translate(0.0f, 0.0f, factor);
|
|
I->m_view.m_clip().m_front -= factor;
|
|
I->m_view.m_clip().m_back -= factor;
|
|
UpdateFrontBackSafe(I);
|
|
}
|
|
invalidate = true;
|
|
}
|
|
break;
|
|
case SDOF_CLIP_MODE:
|
|
if((x != 0.0F) || (y != 0.0F)) {
|
|
float vScale = SceneGetExactScreenVertexScale(G, nullptr);
|
|
float factor = vScale * (I->Height + I->Width) / 2;
|
|
I->m_view.translate(x * factor, y * factor, 0.0f);
|
|
invalidate = true;
|
|
}
|
|
if(z != 0.0F) {
|
|
float factor = ((I->m_view.m_clipSafe().m_front + I->m_view.m_clipSafe().m_back) / 2); /* average distance within visible space */
|
|
if(factor > 0.0F) {
|
|
factor *= z;
|
|
{
|
|
float old_front = I->m_view.m_clip().m_front;
|
|
float old_back = I->m_view.m_clip().m_back;
|
|
float old_origin = -I->m_view.pos().z;
|
|
SceneClip(G, SceneClipMode::Linear, factor, nullptr, 0);
|
|
SceneDoRoving(G, old_front, old_back, old_origin, true, true);
|
|
}
|
|
invalidate = true;
|
|
}
|
|
}
|
|
break;
|
|
case SDOF_DRAG_MODE:
|
|
{
|
|
float v2[3];
|
|
float scale = SettingGetGlobal_f(G, cSetting_sdof_drag_scale);
|
|
|
|
{
|
|
/* when dragging, we treat all axes proportionately */
|
|
float vScale = SceneGetExactScreenVertexScale(G, nullptr);
|
|
float factor = vScale * (I->Height + I->Width) / 2;
|
|
x *= factor;
|
|
y *= factor;
|
|
z *= factor;
|
|
}
|
|
|
|
v2[0] = x * scale;
|
|
v2[1] = y * scale;
|
|
v2[2] = z * scale;
|
|
|
|
/* transform into model coodinate space */
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), v2, v2);
|
|
|
|
EditorDrag(G, nullptr, -1, cButModeMovDrag,
|
|
SettingGetGlobal_i(G, cSetting_state) - 1, nullptr, v2, nullptr);
|
|
}
|
|
break;
|
|
}
|
|
if(invalidate) {
|
|
SceneInvalidate(G);
|
|
if(SettingGetGlobal_b(G, cSetting_roving_origin)) {
|
|
float v2[3];
|
|
SceneGetCenter(G, v2); /* gets position of center of screen */
|
|
SceneOriginSet(G, v2, true);
|
|
}
|
|
if(SettingGetGlobal_b(G, cSetting_roving_detail)) {
|
|
SceneRovingPostpone(G);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SceneRotateScaled(PyMOLGlobals * G, float rx, float ry, float rz, int sdof_mode)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int invalidate = false;
|
|
float axis[3];
|
|
switch (sdof_mode) {
|
|
case SDOF_NORMAL_MODE:
|
|
axis[0] = rx;
|
|
axis[1] = ry;
|
|
axis[2] = rz;
|
|
{
|
|
float angle = length3f(axis);
|
|
normalize3f(axis);
|
|
SceneRotate(G, 60 * angle, axis[0], axis[1], axis[2]);
|
|
}
|
|
break;
|
|
case SDOF_CLIP_MODE:
|
|
if((fabs(rz) > fabs(rx)) || (fabs(rz) > fabs(rx))) {
|
|
rx = 0.0;
|
|
ry = 0.0;
|
|
} else {
|
|
rz = 0.0;
|
|
}
|
|
axis[0] = rx;
|
|
axis[1] = ry;
|
|
axis[2] = 0.0;
|
|
{
|
|
float angle = length3f(axis);
|
|
normalize3f(axis);
|
|
SceneRotate(G, 60 * angle, axis[0], axis[1], axis[2]);
|
|
}
|
|
if(axis[2] != rz) {
|
|
SceneClip(G, SceneClipMode::Scaling, 1.0F + rz, nullptr, 0);
|
|
}
|
|
break;
|
|
case SDOF_DRAG_MODE:
|
|
{
|
|
float scale = SettingGetGlobal_f(G, cSetting_sdof_drag_scale);
|
|
float v1[3], v2[3];
|
|
axis[0] = rx;
|
|
axis[1] = ry;
|
|
axis[2] = rz;
|
|
|
|
EditorReadyDrag(G, SettingGetGlobal_i(G, cSetting_state) - 1);
|
|
|
|
{
|
|
float angle = length3f(axis);
|
|
normalize3f(axis);
|
|
|
|
v1[0] = cPI * (60 * angle / 180.0F) * scale;
|
|
|
|
/* transform into model coodinate space */
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), axis, v2);
|
|
|
|
EditorDrag(G, nullptr, -1, cButModeRotDrag,
|
|
SettingGetGlobal_i(G, cSetting_state) - 1, v1, v2, nullptr);
|
|
invalidate = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if(invalidate) {
|
|
SceneInvalidate(G);
|
|
}
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
|
|
static void SceneClipSetWithDirty(PyMOLGlobals * G, float front, float back, int dirty)
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
// minimum slab
|
|
float minSlab = cSliceMin * I->Scale;
|
|
if(back - front < minSlab) {
|
|
float avg = (back + front) / 2.0;
|
|
back = avg + minSlab / 2.0;
|
|
front = avg - minSlab / 2.0;
|
|
}
|
|
|
|
I->m_view.m_clip().m_front = front;
|
|
I->m_view.m_clip().m_back = back;
|
|
|
|
UpdateFrontBackSafe(I);
|
|
|
|
if(dirty)
|
|
SceneInvalidate(G);
|
|
else
|
|
SceneInvalidateCopy(G, false);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneClipSet(PyMOLGlobals * G, float front, float back)
|
|
{
|
|
SceneClipSetWithDirty(G, front, back, true);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
static SceneClipMode SceneClipGetEnum(pymol::zstring_view mode)
|
|
{
|
|
static const std::unordered_map<pymol::zstring_view, SceneClipMode> modes{
|
|
{"near", SceneClipMode::Near},
|
|
{"far", SceneClipMode::Far},
|
|
{"move", SceneClipMode::Move},
|
|
{"slab", SceneClipMode::Slab},
|
|
{"atoms", SceneClipMode::Atoms},
|
|
{"scaling", SceneClipMode::Scaling},
|
|
{"linear", SceneClipMode::Linear},
|
|
{"near_set", SceneClipMode::Near_Set},
|
|
{"far_set", SceneClipMode::Far_Set},
|
|
};
|
|
|
|
auto it = modes.find(mode);
|
|
return (it == modes.end()) ? SceneClipMode::Invalid : it->second;
|
|
}
|
|
|
|
pymol::Result<> SceneClipFromMode(PyMOLGlobals* G, pymol::zstring_view mode, float movement,
|
|
pymol::zstring_view sele, int state)
|
|
{
|
|
auto plane = SceneClipGetEnum(mode);
|
|
if (plane == SceneClipMode::Invalid) {
|
|
return pymol::make_error("invalid clip mode");
|
|
}
|
|
SceneClip(G, plane, movement, sele.c_str(), state);
|
|
return {};
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneClip(PyMOLGlobals * G, SceneClipMode mode, float movement, const char *sele, int state)
|
|
{ /* 0=front, 1=back */
|
|
CScene *I = G->Scene;
|
|
float avg;
|
|
float mn[3], mx[3], cent[3], v0[3], offset[3], origin[3];
|
|
const auto& pos = I->m_view.pos();
|
|
switch (mode) {
|
|
case SceneClipMode::Near:
|
|
SceneClipSet(G, I->m_view.m_clip().m_front - movement, I->m_view.m_clip().m_back);
|
|
break;
|
|
case SceneClipMode::Far:
|
|
SceneClipSet(G, I->m_view.m_clip().m_front, I->m_view.m_clip().m_back - movement);
|
|
break;
|
|
case SceneClipMode::Move:
|
|
SceneClipSet(G, I->m_view.m_clip().m_front - movement, I->m_view.m_clip().m_back - movement);
|
|
break;
|
|
case SceneClipMode::Slab:
|
|
if(sele[0]) {
|
|
if(!ExecutiveGetExtent(G, sele, mn, mx, true, state, false))
|
|
sele = nullptr;
|
|
else {
|
|
average3f(mn, mx, cent); /* get center of selection */
|
|
subtract3f(cent, glm::value_ptr(I->m_view.origin()), v0); /* how far from origin? */
|
|
MatrixTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), v0, offset); /* convert to view-space */
|
|
}
|
|
} else {
|
|
sele = nullptr;
|
|
}
|
|
avg = (I->m_view.m_clip().m_front + I->m_view.m_clip().m_back) / 2.0F;
|
|
movement /= 2.0F;
|
|
if(sele) {
|
|
avg = -pos.z - offset[2];
|
|
}
|
|
SceneClipSet(G, avg - movement, avg + movement);
|
|
break;
|
|
case SceneClipMode::Atoms:
|
|
if(!sele)
|
|
sele = cKeywordAll;
|
|
else if(!sele[0]) {
|
|
sele = cKeywordAll;
|
|
}
|
|
if(WordMatchExact(G, sele, cKeywordCenter, true)) {
|
|
MatrixTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), glm::value_ptr(I->m_view.origin()), origin); /* convert to view-space */
|
|
SceneClipSet(G, origin[2] - movement, origin[2] + movement);
|
|
} else if(WordMatchExact(G, sele, cKeywordOrigin, true)) {
|
|
SceneClipSet(G, -pos.z - movement, -pos.z + movement);
|
|
} else {
|
|
if(!ExecutiveGetCameraExtent(G, sele, mn, mx, true, state))
|
|
sele = nullptr;
|
|
if(sele) {
|
|
if(sele[0]) {
|
|
average3f(mn, mx, cent); /* get center of selection */
|
|
MatrixTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), glm::value_ptr(I->m_view.origin()), origin); /* convert to view-space */
|
|
subtract3f(mx, origin, mx); /* how far from origin? */
|
|
subtract3f(mn, origin, mn); /* how far from origin? */
|
|
SceneClipSet(G, -pos.z - mx[2] - movement, -pos.z - mn[2] + movement);
|
|
} else {
|
|
sele = nullptr;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case SceneClipMode::Scaling:
|
|
{
|
|
double avg = (I->m_view.m_clip().m_front / 2.0) + (I->m_view.m_clip().m_back / 2.0);
|
|
double width_half = I->m_view.m_clip().m_back - avg;
|
|
double new_w_half = std::min(movement * width_half,
|
|
width_half + 1000.0); // prevent exploding of clipping planes
|
|
|
|
SceneClipSet(G, avg - new_w_half, avg + new_w_half);
|
|
}
|
|
break;
|
|
case SceneClipMode::Proportional:
|
|
{
|
|
float shift = (I->m_view.m_clip().m_front - I->m_view.m_clip().m_back) * movement;
|
|
SceneClipSet(G, I->m_view.m_clip().m_front + shift, I->m_view.m_clip().m_back + shift);
|
|
}
|
|
break;
|
|
case SceneClipMode::Linear:
|
|
{
|
|
SceneClipSet(G, I->m_view.m_clip().m_front + movement, I->m_view.m_clip().m_back + movement);
|
|
}
|
|
break;
|
|
case SceneClipMode::Near_Set:
|
|
{
|
|
SceneClipSet(G, movement, I->m_view.m_clip().m_back);
|
|
}
|
|
break;
|
|
case SceneClipMode::Far_Set:
|
|
{
|
|
SceneClipSet(G, I->m_view.m_clip().m_front, movement);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves clipping plane distances from camera
|
|
* @return near and far clipping plane distances
|
|
* @TODO: Needs camera param index when multiple cameras are implemented
|
|
* @TODO: Return error when camera idx found.
|
|
*/
|
|
pymol::Result<std::pair<float, float>> SceneGetClip(PyMOLGlobals* G)
|
|
{
|
|
auto clip = G->Scene->getSceneView().m_clip;
|
|
return std::make_pair(clip.m_front, clip.m_back);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneSetMatrix(PyMOLGlobals * G, float *m)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->m_view.setRotMatrix(glm::make_mat4(m));
|
|
SceneUpdateInvMatrix(G);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneGetViewNormal(PyMOLGlobals * G, float *v)
|
|
{
|
|
CScene *I = G->Scene;
|
|
copy3f(I->ViewNormal, v);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneGetState(PyMOLGlobals * G)
|
|
{
|
|
return (SettingGetGlobal_i(G, cSetting_state) - 1);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
float *SceneGetMatrix(PyMOLGlobals * G)
|
|
{
|
|
return const_cast<float*>(glm::value_ptr(G->Scene->m_view.rotMatrix()));
|
|
}
|
|
|
|
float *SceneGetPmvMatrix(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
multiply44f44f44f(SceneGetModelViewMatrixPtr(G),
|
|
SceneGetProjectionMatrixPtr(G), I->PmvMatrix);
|
|
return (I->PmvMatrix);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
|
|
GLFramebufferConfig SceneDrawBothGetConfig(PyMOLGlobals* G)
|
|
{
|
|
GLFramebufferConfig config;
|
|
config.framebuffer = G->ShaderMgr->defaultBackbuffer.framebuffer;
|
|
config.drawBuffer = SceneMustDrawBoth(G)
|
|
? GL_BACK_LEFT
|
|
: G->ShaderMgr->defaultBackbuffer.drawBuffer;
|
|
return config;
|
|
}
|
|
|
|
int SceneCaptureWindow(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int ok = true;
|
|
|
|
/* check assumptions */
|
|
|
|
if(ok && G->HaveGUI && G->ValidContext) {
|
|
auto drawBuffer = SceneDrawBothGetConfig(G);
|
|
|
|
ScenePurgeImage(G);
|
|
|
|
SceneCopy(G, drawBuffer, true, true);
|
|
|
|
if(!I->Image)
|
|
ok = false;
|
|
|
|
if(ok && I->Image) {
|
|
I->DirtyFlag = false;
|
|
I->CopyType = 2; /* suppresses display of copied image */
|
|
if(SettingGetGlobal_b(G, cSetting_opaque_background))
|
|
I->Image->m_needs_alpha_reset = true;
|
|
}
|
|
} else {
|
|
ok = false;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Sets the Scene's cached copy image
|
|
* @param image the image to set
|
|
* @param dirty whether the image is dirty (needed?)
|
|
* @param copyForced whether the image was forced to be copied
|
|
*/
|
|
static void SceneSetCopyImage(
|
|
PyMOLGlobals* G, pymol::Image image, bool dirty, bool copyForced)
|
|
{
|
|
auto I = G->Scene;
|
|
I->Image = std::make_shared<pymol::Image>(std::move(image));
|
|
I->CopyType = true;
|
|
I->CopyForced = copyForced;
|
|
I->DirtyFlag = dirty;
|
|
}
|
|
|
|
/**
|
|
* Get the maximum dimensions of the viewport
|
|
* @return the maximum dimensions of OpenGL viewport
|
|
*/
|
|
Extent2D SceneGLGetMaxDimensions(PyMOLGlobals* G)
|
|
{
|
|
//TODO: Move this function to OpenGL Context manager
|
|
GLint dims[2];
|
|
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, reinterpret_cast<GLint*>(&dims));
|
|
return Extent2D{static_cast<std::uint32_t>(dims[0]), static_cast<std::uint32_t>(dims[1])};
|
|
}
|
|
|
|
Extent2D ExtentClampByAspectRatio(Extent2D extent, const Extent2D& maxDim)
|
|
{
|
|
float extentAspect = static_cast<float>(extent.width) / extent.height;
|
|
if (extent.width > maxDim.width) {
|
|
extent.height = static_cast<std::uint32_t>(maxDim.width / extentAspect);
|
|
extent.width = maxDim.width;
|
|
}
|
|
if (extent.height > maxDim.height) {
|
|
extent.width = static_cast<std::uint32_t>(maxDim.height * extentAspect);
|
|
extent.height = maxDim.height;
|
|
}
|
|
return extent;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns supersampled image
|
|
* @param src source image
|
|
* @param factor supersampling factor
|
|
* @param shift shift value
|
|
* @return supersampled image
|
|
*/
|
|
static pymol::Image SceneSupersampleImage(
|
|
const pymol::Image& src, const UpscaledExtentInfo& upscaledExtent)
|
|
{
|
|
auto factor = upscaledExtent.factor;
|
|
auto shift = upscaledExtent.shift;
|
|
auto oriWidth = src.getWidth();
|
|
auto oriHeight = src.getHeight();
|
|
unsigned int src_row_bytes = oriWidth * pymol::Image::getPixelSize();
|
|
|
|
auto width = oriWidth / factor;
|
|
auto height = oriHeight / factor;
|
|
|
|
auto* p = src.bits();
|
|
pymol::Image newImg(width, height);
|
|
unsigned char* q = newImg.bits();
|
|
unsigned int factor_col_bytes = factor * pymol::Image::getPixelSize();
|
|
unsigned int factor_row_bytes = factor * src_row_bytes;
|
|
|
|
// TODO: Rename variables and cleanup -- This was pulled almost 1:1
|
|
for (int b = 0; b < height; b++) { /* rows */
|
|
auto* pp = p;
|
|
for (int a = 0; a < width; a++) { /* cols */
|
|
unsigned int c1{};
|
|
unsigned int c2{};
|
|
unsigned int c3{};
|
|
unsigned int c4{};
|
|
auto* ppp = pp;
|
|
for (int d = 0; d < factor; d++) { /* box rows */
|
|
auto* pppp = ppp;
|
|
for (int c = 0; c < factor; c++) { /* box cols */
|
|
unsigned int alpha = pppp[3];
|
|
c4 += alpha;
|
|
c1 += *(pppp++) * alpha;
|
|
c2 += *(pppp++) * alpha;
|
|
c3 += *(pppp++) * alpha;
|
|
pppp++;
|
|
}
|
|
ppp += src_row_bytes;
|
|
}
|
|
if (c4) { /* divide out alpha channel & average */
|
|
c1 = c1 / c4;
|
|
c2 = c2 / c4;
|
|
c3 = c3 / c4;
|
|
} else { /* alpha zero! so compute average RGB */
|
|
c1 = c2 = c3 = 0;
|
|
ppp = pp;
|
|
for (int d = 0; d < factor; d++) { /* box rows */
|
|
auto* pppp = ppp;
|
|
for (int c = 0; c < factor; c++) { /* box cols */
|
|
c1 += *(pppp++);
|
|
c2 += *(pppp++);
|
|
c3 += *(pppp++);
|
|
pppp++;
|
|
}
|
|
ppp += src_row_bytes;
|
|
}
|
|
c1 = c1 >> shift;
|
|
c2 = c2 >> shift;
|
|
c3 = c3 >> shift;
|
|
}
|
|
*(q++) = c1;
|
|
*(q++) = c2;
|
|
*(q++) = c3;
|
|
*(q++) = c4 >> shift;
|
|
pp += factor_col_bytes;
|
|
}
|
|
p += factor_row_bytes;
|
|
}
|
|
return newImg;
|
|
}
|
|
|
|
pymol::Image GLImageToPyMOLImage(
|
|
PyMOLGlobals* G, const GLFramebufferConfig& config, const Rect2D& srcRect)
|
|
{
|
|
auto imgData = G->ShaderMgr->readPixelsFrom(G, srcRect, config);
|
|
pymol::Image img(srcRect.extent.width, srcRect.extent.height);
|
|
if (!imgData.empty()) {
|
|
img.setVecData(std::move(imgData));
|
|
}
|
|
return img;
|
|
}
|
|
|
|
void PyMOLImageCopy(const pymol::Image& srcImage,
|
|
pymol::Image& dstImage, const Rect2D& srcRect, const Rect2D& dstRect)
|
|
{
|
|
auto srcPx = srcImage.pixels();
|
|
auto dstPx = dstImage.pixels() + (dstRect.offset.x * dstRect.extent.width) +
|
|
(dstRect.offset.y * dstRect.extent.height) * srcRect.extent.width;
|
|
int y_limit;
|
|
int x_limit;
|
|
|
|
if (((dstRect.offset.y + 1) * dstRect.extent.height) > srcRect.extent.height)
|
|
y_limit =
|
|
srcRect.extent.height - (dstRect.offset.y * dstRect.extent.height);
|
|
else
|
|
y_limit = dstRect.extent.height;
|
|
if (((dstRect.offset.x + 1) * dstRect.extent.width) > srcRect.extent.width)
|
|
x_limit = srcRect.extent.width - (dstRect.offset.x * dstRect.extent.width);
|
|
else
|
|
x_limit = dstRect.extent.width;
|
|
for (int a = 0; a < y_limit; a++) {
|
|
std::copy_n(srcPx, x_limit, dstPx);
|
|
srcPx += srcRect.extent.width;
|
|
dstPx += dstRect.extent.width;
|
|
}
|
|
}
|
|
|
|
UpscaledExtentInfo ExtentGetUpscaleInfo(
|
|
PyMOLGlobals* G, Extent2D extent, const Extent2D& maxExtent, int antialias)
|
|
{
|
|
int factor = 0;
|
|
int shift = 0;
|
|
if (antialias == 1) {
|
|
factor = 2;
|
|
shift = 2;
|
|
}
|
|
if (antialias >= 2) {
|
|
factor = 4;
|
|
shift = 4;
|
|
}
|
|
while (factor > 1) {
|
|
if (((extent.width * factor) < maxExtent.width) &&
|
|
((extent.height * factor) < maxExtent.height)) {
|
|
extent.width *= factor;
|
|
extent.height *= factor;
|
|
break;
|
|
} else {
|
|
factor >>= 1;
|
|
shift -= 2;
|
|
if (factor < 2) {
|
|
G->Feedback->autoAdd(FB_Scene, FB_Blather,
|
|
"Scene-Warning: Maximum OpenGL viewport exceeded. Antialiasing "
|
|
"disabled.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (factor < 2) {
|
|
factor = 0;
|
|
}
|
|
|
|
return UpscaledExtentInfo{
|
|
extent = extent,
|
|
factor = factor,
|
|
shift = shift
|
|
};
|
|
}
|
|
|
|
pymol::Result<> SceneMakeSizedImage(PyMOLGlobals* G, Extent2D extent,
|
|
int antialias, bool excludeSelections, SceneRenderWhich renderWhich)
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
float sceneAspectRatio = SceneGetAspectRatio(G);
|
|
|
|
// Calculate from implicit extents if needed
|
|
if (extent.width == 0 && extent.height == 0) {
|
|
extent = SceneGetExtent(G);
|
|
} else if (extent.width != 0 && extent.height == 0) {
|
|
extent.height = static_cast<std::uint32_t>(extent.width / sceneAspectRatio);
|
|
} else if (extent.height != 0 && extent.width == 0) {
|
|
extent.width = static_cast<std::uint32_t>(extent.height * sceneAspectRatio);
|
|
}
|
|
|
|
std::optional<Extent2D> saveExtent;
|
|
if (!((extent.width > 0) && (extent.height > 0) && (I->Width > 0) &&
|
|
(I->Height > 0))) {
|
|
if (saveExtent) {
|
|
SceneSetExtent(G, *saveExtent);
|
|
}
|
|
return pymol::make_error(
|
|
"SceneMakeSizedImage-Error: invalid image dimensions");
|
|
}
|
|
|
|
if (!(G->HaveGUI && G->ValidContext)) {
|
|
if (saveExtent) {
|
|
SceneSetExtent(G, *saveExtent);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
auto maxDim = SceneGLGetMaxDimensions(G);
|
|
auto clampedExtents = ExtentClampByAspectRatio(extent, maxDim);
|
|
auto upscaledExtentInfo =
|
|
ExtentGetUpscaleInfo(G, clampedExtents, maxDim, antialias);
|
|
extent = upscaledExtentInfo.extent;
|
|
|
|
if (!saveExtent) {
|
|
saveExtent = SceneGetExtent(G);
|
|
}
|
|
|
|
SceneSetExtent(G, extent);
|
|
G->ShaderMgr->bindOffscreenSizedImage(extent, true);
|
|
|
|
int nXStep = (extent.width / (I->Width + 1)) + 1;
|
|
int nYStep = (extent.height / (I->Height + 1)) + 1;
|
|
|
|
pymol::Image final_image(extent.width, extent.height);
|
|
|
|
int total_steps = nXStep * nYStep;
|
|
|
|
OrthoBusyPrime(G);
|
|
|
|
/* so the trick here is that we need to move the camera around
|
|
so that we get a pixel-perfect mosaic */
|
|
for (int y = 0; y < nYStep; y++) {
|
|
int y_offset = -(I->Height * y);
|
|
|
|
for (int x = 0; x < nXStep; x++) {
|
|
int x_offset = -(I->Width * x);
|
|
|
|
OrthoBusyFast(G, y * nXStep + x, total_steps);
|
|
|
|
auto offscreenFBO = G->ShaderMgr->bindOffscreenSizedImage(extent, true);
|
|
|
|
Rect2D viewport{};
|
|
// No offset since this is now done offscreen. In the future,
|
|
// if there's ever a desire to do this in-place, then the
|
|
// offset should be applied.
|
|
viewport.extent = extent;
|
|
SceneRenderInfo renderInfo{};
|
|
renderInfo.mousePos = Offset2D{x_offset, y_offset};
|
|
renderInfo.viewportOverride = viewport;
|
|
renderInfo.excludeSelections = excludeSelections;
|
|
renderInfo.renderWhich = renderWhich;
|
|
renderInfo.offscreenConfig = offscreenFBO;
|
|
G->ShaderMgr->setDrawBuffer(offscreenFBO);
|
|
|
|
// Top-level configs are used by PP shaders.
|
|
// Temporarily swap them out and restore.
|
|
// TODO: This ought to be done in a stack-manner
|
|
// in place of the GPUMgr's framegraph.
|
|
auto oldTLConfig = G->ShaderMgr->topLevelConfig;
|
|
G->ShaderMgr->topLevelConfig = offscreenFBO;
|
|
|
|
// JJ: SceneSetViewport in SceneRender will extract the glViewport
|
|
// rather than the Scene extent. Unsure why this is done, so for now
|
|
// just preset the viewport.
|
|
SceneSetViewport(G, viewport);
|
|
SceneRender(G, renderInfo);
|
|
|
|
G->ShaderMgr->topLevelConfig = oldTLConfig;
|
|
|
|
Rect2D srcRect{};
|
|
srcRect.extent = extent;
|
|
auto image = GLImageToPyMOLImage(G, offscreenFBO, srcRect);
|
|
|
|
if (!image.empty()) {
|
|
Rect2D dstRect{{x, y}, SceneGetExtent(G)};
|
|
PyMOLImageCopy(image, final_image, srcRect, dstRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!OrthoDeferredWaiting(G)) {
|
|
if (SettingGet<int>(G, cSetting_draw_mode) == -2) {
|
|
ExecutiveSetSettingFromString(
|
|
G, cSetting_draw_mode, "-1", "", -1, true, true);
|
|
SceneUpdate(G, false);
|
|
}
|
|
}
|
|
|
|
SceneInvalidateCopy(G, true);
|
|
|
|
if (upscaledExtentInfo.factor != 0) { /* are we oversampling? */
|
|
final_image = SceneSupersampleImage(final_image, upscaledExtentInfo);
|
|
}
|
|
|
|
ScenePurgeImage(G);
|
|
|
|
SceneSetCopyImage(G, std::move(final_image), false, true);
|
|
|
|
if (SettingGet<bool>(G, cSetting_opaque_background)) {
|
|
I->Image->m_needs_alpha_reset = true;
|
|
}
|
|
|
|
if (saveExtent) {
|
|
SceneSetExtent(G, *saveExtent);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Prepares the scene image for PNG export.
|
|
*
|
|
* If there is no rendered image (`G->Scene->CopyType` is false) then read the
|
|
* current image from the OpenGL back buffer.
|
|
*
|
|
* Sets the alpha channel to opaque if the `opaque_background` setting is on.
|
|
*
|
|
* Modifies `G->Scene->Image`.
|
|
*
|
|
* @param prior_only Return nullptr if there is no prior image (`G->Scene->Image` is nullptr)
|
|
* @return The scene image
|
|
*/
|
|
pymol::Image* SceneImagePrepare(PyMOLGlobals* G, bool prior_only)
|
|
{
|
|
CScene *I = G->Scene;
|
|
pymol::Image* image = nullptr;
|
|
int save_stereo = (I->StereoMode == 1);
|
|
|
|
if (!(I->CopyType || prior_only)) {
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
ScenePurgeImage(G);
|
|
I->Image = std::make_shared<pymol::Image>(I->Width, I->Height, save_stereo);
|
|
image = I->Image.get();
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if(SceneMustDrawBoth(G) || save_stereo) {
|
|
glReadBuffer(GL_BACK_LEFT);
|
|
} else {
|
|
glReadBuffer(G->ShaderMgr->defaultBackbuffer.drawBuffer); // GL_BACK
|
|
}
|
|
#endif
|
|
PyMOLReadPixels(I->rect.left, I->rect.bottom, I->Width, I->Height,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *) (image->bits()));
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if(save_stereo) {
|
|
glReadBuffer(GL_BACK_RIGHT);
|
|
PyMOLReadPixels(I->rect.left, I->rect.bottom, I->Width, I->Height,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *) (image->bits() + image->getSizeInBytes()));
|
|
}
|
|
#endif
|
|
I->Image->m_needs_alpha_reset = true;
|
|
}
|
|
} else if(I->Image) {
|
|
image = I->Image.get();
|
|
}
|
|
if(image) {
|
|
int opaque_back = SettingGetGlobal_b(G, cSetting_opaque_background);
|
|
if(opaque_back && I->Image->m_needs_alpha_reset) {
|
|
int i, s = image->getSizeInBytes() * (image->isStereo() ? 2 : 1);
|
|
for (i = pymol::Image::Channel::ALPHA; i < s;
|
|
i += pymol::Image::getPixelSize())
|
|
image->bits()[i] = 0xFF;
|
|
I->Image->m_needs_alpha_reset = false;
|
|
}
|
|
}
|
|
return image;
|
|
}
|
|
|
|
/**
|
|
* Get the size of the rendered image. This is either identical to
|
|
* cmd.get_viewport(), or the dimensions which were last passed to
|
|
* cmd.draw(), cmd.ray() or cmd.png().
|
|
*/
|
|
std::pair<int, int> SceneGetImageSize(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if (I->CopyType && I->Image) {
|
|
return I->Image->getSize();
|
|
} else {
|
|
return std::make_pair(I->Width, I->Height);
|
|
}
|
|
}
|
|
|
|
float SceneGetGridAspectRatio(PyMOLGlobals * G){
|
|
CScene *I = G->Scene;
|
|
auto gridAspRat = (float)(I->grid.cur_viewport_size.width / (float)I->grid.cur_viewport_size.height);
|
|
return SceneGetAspectRatio(G) / gridAspRat;
|
|
}
|
|
|
|
int SceneCopyExternal(PyMOLGlobals * G, int width, int height,
|
|
int rowbytes, unsigned char *dest, int mode)
|
|
{
|
|
auto image = SceneImagePrepare(G, false);
|
|
CScene *I = G->Scene;
|
|
int result = false;
|
|
int i, j;
|
|
int premultiply_alpha = true;
|
|
int red_index = 0, blue_index = 1, green_index = 2, alpha_index = 3;
|
|
int no_alpha = (SettingGetGlobal_b(G, cSetting_opaque_background) &&
|
|
SettingGetGlobal_b(G, cSetting_ray_opaque_background));
|
|
|
|
if(mode & 0x1) {
|
|
int index = 0;
|
|
while(index < 4) {
|
|
if(dest[index] == 'R')
|
|
red_index = index;
|
|
if(dest[index] == 'G')
|
|
green_index = index;
|
|
if(dest[index] == 'B')
|
|
blue_index = index;
|
|
if(dest[index] == 'A')
|
|
alpha_index = index;
|
|
index++;
|
|
}
|
|
}
|
|
if(mode & 0x2) {
|
|
premultiply_alpha = false;
|
|
}
|
|
/*
|
|
printf("image %p I->image %p\n");
|
|
if(I->Image) {
|
|
printf("%d %d %d %d\n",I->Image->width,width,I->Image->height,height);
|
|
} */
|
|
|
|
if(image && I->Image && (I->Image->getWidth() == width) && (I->Image->getHeight() == height)) {
|
|
for(i = 0; i < height; i++) {
|
|
unsigned char *src = image->bits() + ((height - 1) - i) * width * 4;
|
|
unsigned char *dst;
|
|
if(mode & 0x4) {
|
|
dst = dest + (height - (i + 1)) * (rowbytes);
|
|
} else {
|
|
dst = dest + i * (rowbytes);
|
|
}
|
|
for(j = 0; j < width; j++) {
|
|
if(no_alpha) {
|
|
dst[red_index] = src[0]; /* no alpha */
|
|
dst[green_index] = src[1];
|
|
dst[blue_index] = src[2];
|
|
dst[alpha_index] = 0xFF;
|
|
/* if(!(i||j)) {
|
|
printf("no alpha\n");
|
|
} */
|
|
} else if(premultiply_alpha) {
|
|
dst[red_index] = (((unsigned int) src[0]) * src[3]) / 255; /* premultiply alpha */
|
|
dst[green_index] = (((unsigned int) src[1]) * src[3]) / 255;
|
|
dst[blue_index] = (((unsigned int) src[2]) * src[3]) / 255;
|
|
dst[alpha_index] = src[3];
|
|
/* if(!(i||j)) {
|
|
printf("premult alpha\n");
|
|
} */
|
|
} else {
|
|
dst[red_index] = src[0]; /* standard alpha */
|
|
dst[green_index] = src[1];
|
|
dst[blue_index] = src[2];
|
|
dst[alpha_index] = src[3];
|
|
/* if(!(i||j)) {
|
|
printf("standard alpha\n");
|
|
} */
|
|
}
|
|
dst += 4;
|
|
src += 4;
|
|
}
|
|
}
|
|
result = true;
|
|
} else {
|
|
printf("image or size mismatch\n");
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
bool ScenePNG(PyMOLGlobals* G, pymol::zstring_view png, float dpi, int quiet,
|
|
int prior_only, int format, png_outbuf_t* outbuf)
|
|
{
|
|
CScene *I = G->Scene;
|
|
SceneImagePrepare(G, prior_only);
|
|
if(I->Image) {
|
|
int width, height;
|
|
std::tie(width, height) = I->Image->getSize();
|
|
auto saveImage = I->Image;
|
|
if(I->Image->isStereo()) {
|
|
saveImage = std::make_shared<pymol::Image>();
|
|
*(saveImage) = I->Image->interlace();
|
|
}
|
|
if(dpi < 0.0F)
|
|
dpi = SettingGetGlobal_f(G, cSetting_image_dots_per_inch);
|
|
auto screen_gamma = SettingGetGlobal_f(G, cSetting_png_screen_gamma);
|
|
auto file_gamma = SettingGetGlobal_f(G, cSetting_png_file_gamma);
|
|
if(MyPNGWrite(png, *saveImage, dpi, format, quiet, screen_gamma, file_gamma, outbuf)) {
|
|
if(!quiet) {
|
|
PRINTFB(G, FB_Scene, FB_Actions)
|
|
" %s: wrote %dx%d pixel image to file \"%s\".\n", __func__,
|
|
width, I->Image->getHeight(), png.c_str() ENDFB(G);
|
|
}
|
|
} else {
|
|
PRINTFB(G, FB_Scene, FB_Errors)
|
|
" %s-Error: error writing \"%s\"! Please check directory...\n", __func__,
|
|
png.c_str() ENDFB(G);
|
|
}
|
|
}
|
|
return I->Image.get() != nullptr;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneGetFrame(PyMOLGlobals * G)
|
|
{
|
|
if(MovieDefined(G))
|
|
return (SettingGetGlobal_i(G, cSetting_frame) - 1);
|
|
else
|
|
return (SettingGetGlobal_i(G, cSetting_state) - 1);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
* Returns the number of movie frames, or the number of states if no movie
|
|
* is defined.
|
|
*/
|
|
int SceneCountFrames(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int mov_len = MovieGetLength(G);
|
|
I->HasMovie = (mov_len != 0);
|
|
if(mov_len > 0) {
|
|
I->NFrame = mov_len;
|
|
} else {
|
|
if (mov_len < 0) {
|
|
// allows you to see cached movie even w/o object
|
|
I->NFrame = -mov_len;
|
|
} else {
|
|
I->NFrame = 0;
|
|
}
|
|
|
|
for (const auto& obj : I->Obj) {
|
|
int n = obj->getNFrame();
|
|
if (n > I->NFrame)
|
|
I->NFrame = n;
|
|
}
|
|
}
|
|
PRINTFD(G, FB_Scene)" %s: leaving... I->NFrame %d\n", __func__, I->NFrame ENDFD
|
|
return I->NFrame;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneSetFrame(PyMOLGlobals * G, int mode, int frame)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int newFrame;
|
|
int newState = 0;
|
|
int movieCommand = false;
|
|
int suppress = false;
|
|
|
|
newFrame = SettingGetGlobal_i(G, cSetting_frame) - 1;
|
|
PRINTFD(G, FB_Scene)
|
|
" %s: entered.\n", __func__ ENDFD;
|
|
switch (mode) {
|
|
case -1: /* movie/frame override - go to this state absolutely! */
|
|
newState = frame;
|
|
break;
|
|
case 0: /* absolute frame */
|
|
newFrame = frame;
|
|
break;
|
|
case 1: /* relative frame */
|
|
newFrame += frame;
|
|
break;
|
|
case 2: /* end */
|
|
newFrame = I->NFrame - 1;
|
|
break;
|
|
case 3: /* middle with automatic movie command */
|
|
newFrame = I->NFrame / 2;
|
|
movieCommand = true;
|
|
break;
|
|
case 4: /* absolute with automatic movie command */
|
|
newFrame = frame;
|
|
movieCommand = true;
|
|
break;
|
|
case 5: /* relative with automatic movie command */
|
|
newFrame += frame;
|
|
movieCommand = true;
|
|
break;
|
|
case 6: /* end with automatic movie command */
|
|
newFrame = I->NFrame - 1;
|
|
movieCommand = true;
|
|
break;
|
|
case 7: /* absolute with forced movie command */
|
|
newFrame = frame;
|
|
movieCommand = true;
|
|
break;
|
|
case 8: /* relative with forced movie command */
|
|
newFrame += frame;
|
|
movieCommand = true;
|
|
break;
|
|
case 9: /* end with forced movie command */
|
|
newFrame = I->NFrame - 1;
|
|
movieCommand = true;
|
|
break;
|
|
case 10: /* seek forward to current scene (if present) */
|
|
{
|
|
frame = MovieSeekScene(G,true);
|
|
if(frame>=0) {
|
|
newFrame = frame;
|
|
movieCommand = true;
|
|
} else {
|
|
suppress = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if(!suppress) {
|
|
SceneCountFrames(G);
|
|
if(mode >= 0) {
|
|
if(newFrame >= I->NFrame)
|
|
newFrame = I->NFrame - 1;
|
|
if(newFrame < 0)
|
|
newFrame = 0;
|
|
newState = MovieFrameToIndex(G, newFrame);
|
|
if(newFrame == 0) {
|
|
if(MovieMatrix(G, cMovieMatrixRecall)) {
|
|
SceneAbortAnimation(G); /* if we have a programmed initial
|
|
orientation, don't allow animation
|
|
to override it */
|
|
}
|
|
}
|
|
SettingSetGlobal_i(G, cSetting_frame, newFrame + 1);
|
|
SettingSetGlobal_i(G, cSetting_state, newState + 1);
|
|
ExecutiveInvalidateSelectionIndicatorsCGO(G);
|
|
SceneInvalidatePicking(G);
|
|
if(movieCommand) {
|
|
MovieDoFrameCommand(G, newFrame);
|
|
MovieFlushCommands(G);
|
|
}
|
|
if(SettingGetGlobal_b(G, cSetting_cache_frames))
|
|
I->MovieFrameFlag = true;
|
|
} else {
|
|
SettingSetGlobal_i(G, cSetting_frame, newFrame + 1);
|
|
SettingSetGlobal_i(G, cSetting_state, newState + 1);
|
|
ExecutiveInvalidateSelectionIndicatorsCGO(G);
|
|
SceneInvalidatePicking(G);
|
|
}
|
|
MovieSetScrollBarFrame(G, newFrame);
|
|
SeqChanged(G); // SceneInvalidate(G);
|
|
}
|
|
PRINTFD(G, FB_Scene)
|
|
" %s: leaving...\n", __func__ ENDFD;
|
|
OrthoInvalidateDoDraw(G);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneDirty(PyMOLGlobals * G)
|
|
|
|
/* This means that the current image on the screen (and/or in the buffer)
|
|
|
|
needs to be updated */
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
PRINTFD(G, FB_Scene)
|
|
" %s: called.\n", __func__ ENDFD;
|
|
|
|
if(I) {
|
|
if(!I->DirtyFlag) {
|
|
I->DirtyFlag = true;
|
|
/* SceneInvalidateCopy(G,false); */
|
|
OrthoDirty(G);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SceneRovingPostpone(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float delay;
|
|
if(SettingGetGlobal_b(G, cSetting_roving_detail)) {
|
|
delay = SettingGetGlobal_f(G, cSetting_roving_delay);
|
|
if(delay < 0.0F) {
|
|
I->RovingLastUpdate = UtilGetSeconds(G); /* put off delay */
|
|
}
|
|
}
|
|
}
|
|
|
|
void SceneRovingDirty(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
if(SettingGetGlobal_b(G, cSetting_roving_detail)) {
|
|
SceneRovingPostpone(G);
|
|
I->RovingDirtyFlag = true;
|
|
}
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneChanged(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->ChangedFlag = true;
|
|
SceneInvalidateCopy(G, false);
|
|
SceneDirty(G);
|
|
SeqChanged(G);
|
|
PyMOL_NeedRedisplay(G->PyMOL);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
Block *SceneGetBlock(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
return (I);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneValidateImageMode(PyMOLGlobals * G, int mode, bool defaultdraw) {
|
|
switch (mode) {
|
|
case cSceneImage_Normal:
|
|
case cSceneImage_Draw:
|
|
case cSceneImage_Ray:
|
|
return mode;
|
|
}
|
|
|
|
if (mode != cSceneImage_Default) {
|
|
PRINTFB(G, FB_Scene, FB_Warnings)
|
|
" %s-Warning: invalid mode %d\n", __FUNCTION__, mode ENDFB(G);
|
|
}
|
|
|
|
if(!G->HaveGUI || SettingGetGlobal_b(G, cSetting_ray_trace_frames)) {
|
|
return cSceneImage_Ray;
|
|
}
|
|
|
|
if(defaultdraw || SettingGetGlobal_b(G, cSetting_draw_frames)) {
|
|
return cSceneImage_Draw;
|
|
}
|
|
|
|
return cSceneImage_Normal;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneMakeMovieImage(PyMOLGlobals * G,
|
|
int show_timing,
|
|
int validate,
|
|
int mode,
|
|
int width,
|
|
int height)
|
|
{
|
|
CScene *I = G->Scene;
|
|
auto requestedExtent = Extent2D{
|
|
static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height)};
|
|
// float *v;
|
|
int valid = true;
|
|
PRINTFB(G, FB_Scene, FB_Blather)
|
|
" Scene: Making movie image.\n" ENDFB(G);
|
|
|
|
// PYMOL-3209 objects inside hidden groups become visible
|
|
ExecutiveUpdateSceneMembers(G);
|
|
|
|
mode = SceneValidateImageMode(G, mode, width || height);
|
|
|
|
I->DirtyFlag = false;
|
|
switch (mode) {
|
|
case cSceneImage_Ray:
|
|
SceneRay(G, width, height, SettingGetGlobal_i(G, cSetting_ray_default_renderer),
|
|
nullptr, nullptr, 0.0F, 0.0F, false, nullptr, show_timing, -1);
|
|
break;
|
|
case cSceneImage_Draw:
|
|
SceneMakeSizedImage(G, requestedExtent,
|
|
SettingGet<int>(G, cSetting_antialias), /*excludeSelections*/ false);
|
|
break;
|
|
case cSceneImage_Normal:
|
|
{
|
|
auto drawBuffer = SceneDrawBothGetConfig(G);
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
G->ShaderMgr->setDrawBuffer(drawBuffer);
|
|
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
|
/* insert OpenGL context validation code here? */
|
|
SceneRenderInfo renderInfo{};
|
|
SceneRender(G, renderInfo);
|
|
SceneGLClearColor(0.0, 0.0, 0.0, 1.0);
|
|
SceneCopy(G, drawBuffer, true, false);
|
|
/* insert OpenGL context validation code here? */
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
MovieSetImage(G,
|
|
MovieFrameToImage(G, SettingGetGlobal_i(G, cSetting_frame) - 1),
|
|
I->Image);
|
|
if(I->Image)
|
|
I->CopyType = true;
|
|
return valid;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
static void SceneUpdateCameraRock(PyMOLGlobals * G, int dirty)
|
|
{
|
|
|
|
CScene *I = G->Scene;
|
|
float ang_cur, disp, diff;
|
|
float sweep_angle = SettingGetGlobal_f(G, cSetting_sweep_angle);
|
|
float sweep_speed = SettingGetGlobal_f(G, cSetting_sweep_speed);
|
|
float sweep_phase = SettingGetGlobal_f(G, cSetting_sweep_phase);
|
|
int sweep_mode = SettingGetGlobal_i(G, cSetting_sweep_mode);
|
|
float shift = (float) (PI / 2.0F);
|
|
|
|
I->SweepTime += I->RenderTime;
|
|
I->LastSweepTime = UtilGetSeconds(G);
|
|
|
|
switch (sweep_mode) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
if(sweep_angle <= 0.0F) {
|
|
diff = (float) ((PI / 180.0F) * I->RenderTime * 10 * sweep_speed / 0.75F);
|
|
} else {
|
|
ang_cur = (float) (I->SweepTime * sweep_speed) + sweep_phase;
|
|
disp = (float) (sweep_angle * (PI / 180.0F) * sin(ang_cur) / 2);
|
|
diff = (float) (disp - I->LastSweep);
|
|
I->LastSweep = disp;
|
|
}
|
|
switch (sweep_mode) {
|
|
case 0:
|
|
SceneRotateWithDirty(G, (float) (180 * diff / PI), 0.0F, 1.0F, 0.0F, dirty);
|
|
break;
|
|
case 1:
|
|
SceneRotateWithDirty(G, (float) (180 * diff / PI), 1.0F, 0.0F, 0.0F, dirty);
|
|
break;
|
|
case 2: /* z-rotation...useless! */
|
|
SceneRotateWithDirty(G, (float) (180 * diff / PI), 0.0F, 0.0F, 1.0F, dirty);
|
|
break;
|
|
}
|
|
break;
|
|
case 3: /* nutate */
|
|
SceneRotateWithDirty(G, (float) (-I->LastSweepY), 0.0F, 1.0F, 0.0F, dirty);
|
|
SceneRotateWithDirty(G, (float) (-I->LastSweepX), 1.0F, 0.0F, 0.0F, dirty);
|
|
ang_cur = (float) (I->SweepTime * sweep_speed) + sweep_phase;
|
|
|
|
I->LastSweepX = (float) (sweep_angle * sin(ang_cur) / 2);
|
|
I->LastSweepY = (float) (sweep_angle * sin(ang_cur + shift) / 2);
|
|
|
|
if(I->SweepTime * sweep_speed < PI) {
|
|
float factor = (float) ((I->SweepTime * sweep_speed) / PI);
|
|
I->LastSweepX *= factor;
|
|
I->LastSweepY *= factor;
|
|
}
|
|
SceneRotateWithDirty(G, (float) I->LastSweepX, 1.0F, 0.0F, 0.0F, dirty);
|
|
SceneRotateWithDirty(G, (float) I->LastSweepY, 0.0F, 1.0F, 0.0F, dirty);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneIdle(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
double renderTime;
|
|
double minTime;
|
|
int frameFlag = false;
|
|
|
|
if(I->PossibleSingleClick == 2) {
|
|
double now = UtilGetSeconds(G);
|
|
double single_click_delay = I->SingleClickDelay;
|
|
double diff = now - I->LastReleaseTime;
|
|
if(diff > single_click_delay) {
|
|
/* post a single click processing event */
|
|
SceneDeferClickWhen(I, I->LastButton + P_GLUT_SINGLE_LEFT, I->LastWinX, I->LastWinY, I->LastClickTime, I->LastMod); /* push a click onto the queue */
|
|
|
|
I->PossibleSingleClick = 0;
|
|
OrthoDirty(G); /* force an update */
|
|
}
|
|
}
|
|
if(!OrthoDeferredWaiting(G)) {
|
|
if(MoviePlaying(G)) {
|
|
renderTime = UtilGetSeconds(G) - I->LastFrameTime;
|
|
{
|
|
float fps = SettingGetGlobal_f(G, cSetting_movie_fps);
|
|
if(fps <= 0.0F) {
|
|
if(fps < 0.0)
|
|
minTime = 0.0; /* negative fps means full speed */
|
|
else /* 0 fps means use movie_delay instead */
|
|
minTime = SettingGetGlobal_f(G, cSetting_movie_delay) / 1000.0;
|
|
if(minTime >= 0)
|
|
fps = 1.0 / minTime;
|
|
else
|
|
fps = 1000.0F;
|
|
} else {
|
|
minTime = 1.0 / fps;
|
|
}
|
|
if(renderTime >= (minTime - I->LastFrameAdjust)) {
|
|
float adjust = (renderTime - minTime);
|
|
if((fabs(adjust) < minTime) && (fabs(I->LastFrameAdjust) < minTime)) {
|
|
float new_adjust = (renderTime - minTime) + I->LastFrameAdjust;
|
|
I->LastFrameAdjust = (new_adjust + fps * I->LastFrameAdjust) / (1 + fps);
|
|
} else {
|
|
I->LastFrameAdjust = 0.0F;
|
|
}
|
|
frameFlag = true;
|
|
}
|
|
}
|
|
} else if(ControlRocking(G)) {
|
|
renderTime = -I->LastSweepTime + UtilGetSeconds(G);
|
|
minTime = SettingGetGlobal_f(G, cSetting_rock_delay) / 1000.0;
|
|
if(renderTime >= minTime) {
|
|
I->RenderTime = renderTime;
|
|
SceneUpdateCameraRock(G, true);
|
|
}
|
|
}
|
|
|
|
if(MoviePlaying(G) && frameFlag) {
|
|
I->LastFrameTime = UtilGetSeconds(G);
|
|
if((SettingGetGlobal_i(G, cSetting_frame) - 1) == (I->NFrame - 1)) {
|
|
if(SettingGetGlobal_b(G, cSetting_movie_loop)) {
|
|
SceneSetFrame(G, 7, 0);
|
|
} else
|
|
MoviePlay(G, cMovieStop);
|
|
} else {
|
|
SceneSetFrame(G, 5, 1);
|
|
}
|
|
PyMOL_NeedRedisplay(G->PyMOL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
* Zoom to location and radius
|
|
*/
|
|
void SceneWindowSphere(PyMOLGlobals * G, const float *location, float radius)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float v0[3];
|
|
auto pos = I->m_view.pos();
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf ==>\n",
|
|
"WindowSphere BEFORE",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
|
|
if (I->StereoMode == cStereo_openvr) {
|
|
I->Scale = 1.0f / radius;
|
|
radius = 1.0;
|
|
} else {
|
|
I->Scale = 1.0;
|
|
}
|
|
float dist = 2.f * radius / GetFovWidth(G);
|
|
|
|
/* find where this point is in relationship to the origin */
|
|
subtract3f(glm::value_ptr(I->m_view.origin()), location, v0);
|
|
|
|
MatrixTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), v0, glm::value_ptr(pos)); /* convert to view-space */
|
|
|
|
if (I->Height > I->Width && I->Height && I->Width)
|
|
dist *= (float)I->Height / (float)I->Width;
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
/*lift up molecule to the user's head*/
|
|
if (I->StereoMode == cStereo_openvr) {
|
|
pos.x *= I->Scale;
|
|
pos.y = pos.y * I->Scale + 1.0f; //FIXME make it smart
|
|
pos.z *= I->Scale;
|
|
}
|
|
#endif
|
|
|
|
pos.z -= dist;
|
|
I->m_view.m_clip().m_front = (-pos.z - radius * 1.2F);
|
|
I->m_view.m_clip().m_back = (-pos.z + radius * 1.2F);
|
|
UpdateFrontBackSafe(I);
|
|
SceneRovingDirty(G);
|
|
|
|
#ifdef _OPENVR_STEREO_DEBUG_VIEWS
|
|
printf("%-20s IP: %11lf %11lf %11lf, IF/IB: %11lf %11lf, IS: %11lf ==>\n",
|
|
"WindowSphere AFTER",
|
|
pos.x, pos.y, pos.z,
|
|
I->m_view.m_clip().m_front, I->m_view.m_clip().m_back, I->Scale
|
|
);
|
|
#endif // _OPENVR_STEREO_DEBUG_VIEWS
|
|
I->m_view.setPos(pos);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneRelocate(PyMOLGlobals * G, const float *location)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float v0[3];
|
|
auto pos = I->m_view.pos();
|
|
|
|
auto slab_width = I->m_view.m_clip().m_back - I->m_view.m_clip().m_front;
|
|
|
|
/* find out how far camera was from previous origin */
|
|
auto dist = pos.z;
|
|
|
|
// stay in front of camera, empirical value to show at least 1 bond
|
|
if (dist > -5.f && I->StereoMode != cStereo_openvr)
|
|
dist = -5.f;
|
|
|
|
/* find where this point is in relationship to the origin */
|
|
subtract3f(glm::value_ptr(I->m_view.origin()), location, v0);
|
|
|
|
/* printf("%8.3f %8.3f %8.3f\n",I->m_view.m_clip().m_front,pos.z,I->m_view.m_clip().m_back); */
|
|
|
|
auto pos_ptr = glm::value_ptr(pos);
|
|
MatrixTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), v0, pos_ptr); /* convert to view-space */
|
|
|
|
pos.z = dist;
|
|
if (I->StereoMode == cStereo_openvr) {
|
|
pos += glm::vec3(0.0f, 1.0f, 0.0f);
|
|
}
|
|
I->m_view.m_clip().m_front = (-pos.z - (slab_width * 0.50F));
|
|
I->m_view.m_clip().m_back = (-pos.z + (slab_width * 0.50F));
|
|
I->m_view.setPos(pos);
|
|
UpdateFrontBackSafe(I);
|
|
SceneRovingDirty(G);
|
|
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
* Get the origin of rotation in model space
|
|
* cmd.get_view()[12:15]
|
|
*
|
|
* @param[out] origin
|
|
*/
|
|
void SceneOriginGet(PyMOLGlobals * G, float *origin)
|
|
{
|
|
CScene *I = G->Scene;
|
|
copy3f(glm::value_ptr(I->m_view.origin()), origin);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
* Set the origin of rotation in model space
|
|
* (`cmd.get_view()[12:15]`)
|
|
*
|
|
* @param origin New origin
|
|
* @param preserve preserve current viewing location
|
|
*/
|
|
void SceneOriginSet(PyMOLGlobals * G, const float *origin, int preserve)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float v0[3];
|
|
glm::vec3 v1;
|
|
|
|
if(preserve) { /* preserve current viewing location */
|
|
subtract3f(origin, glm::value_ptr(I->m_view.origin()), v0); /* model-space translation */
|
|
MatrixTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), v0, glm::value_ptr(v1)); /* convert to view-space */
|
|
I->m_view.translate(v1); /* offset view to compensate */
|
|
}
|
|
I->m_view.setOrigin(origin[0], origin[1], origin[2]); /* move origin */
|
|
SceneInvalidate(G);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneObjectAdd(PyMOLGlobals * G, pymol::CObject * obj)
|
|
{
|
|
CScene *I = G->Scene;
|
|
obj->Enabled = true;
|
|
I->Obj.push_back(obj);
|
|
if(obj->type == cObjectGadget) {
|
|
I->GadgetObjs.push_back(obj);
|
|
} else {
|
|
I->NonGadgetObjs.push_back(obj);
|
|
}
|
|
SceneCountFrames(G);
|
|
SceneChanged(G);
|
|
SceneInvalidatePicking(G); // PYMOL-2793
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneObjectIsActive(PyMOLGlobals * G, pymol::CObject * obj)
|
|
{
|
|
int result = false;
|
|
CScene *I = G->Scene;
|
|
if (find(I->Obj.begin(), I->Obj.end(), obj) != I->Obj.end())
|
|
result = true;
|
|
return result;
|
|
}
|
|
|
|
int SceneObjectDel(PyMOLGlobals * G, pymol::CObject * obj, int allow_purge)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);
|
|
|
|
if(!obj) { /* deletes all members */
|
|
if(allow_purge && (defer_builds_mode >= 3)) {
|
|
for (auto& obj : I->Obj) {
|
|
/* purge graphics representation when no longer used */
|
|
obj->invalidate(cRepAll, cRepInvPurge, -1);
|
|
}
|
|
}
|
|
I->Obj.clear();
|
|
I->GadgetObjs.clear();
|
|
I->NonGadgetObjs.clear();
|
|
} else {
|
|
auto &obj_list = (obj->type == cObjectGadget) ? I->GadgetObjs : I->NonGadgetObjs;
|
|
auto itg = find(obj_list.begin(), obj_list.end(), obj);
|
|
if (itg != obj_list.end())
|
|
obj_list.erase(itg);
|
|
|
|
auto it = find(I->Obj.begin(), I->Obj.end(), obj);
|
|
if (it != I->Obj.end()){
|
|
if(allow_purge && (defer_builds_mode >= 3)) {
|
|
/* purge graphics representation when no longer used */
|
|
(*it)->invalidate(cRepAll, cRepInvPurge, -1);
|
|
}
|
|
obj->Enabled = false;
|
|
I->Obj.erase(it);
|
|
}
|
|
}
|
|
SceneCountFrames(G);
|
|
SceneInvalidate(G);
|
|
SceneInvalidatePicking(G);
|
|
return 0;
|
|
}
|
|
|
|
bool SceneObjectRemove(PyMOLGlobals* G, pymol::CObject* obj)
|
|
{
|
|
if (obj == nullptr) {
|
|
return true;
|
|
}
|
|
CScene* I = G->Scene;
|
|
auto& obj_list =
|
|
(obj->type == cObjectGadget) ? I->GadgetObjs : I->NonGadgetObjs;
|
|
auto it = std::find(obj_list.begin(), obj_list.end(), obj);
|
|
if(it == obj_list.end()){
|
|
return false;
|
|
}
|
|
obj_list.erase(it, obj_list.end());
|
|
return true;
|
|
}
|
|
|
|
/*========================================================================*/
|
|
int SceneLoadPNG(PyMOLGlobals * G, const char *fname, int movie_flag, int stereo, int quiet)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int ok = false;
|
|
if(I->Image) {
|
|
ScenePurgeImage(G);
|
|
I->CopyType = false;
|
|
OrthoInvalidateDoDraw(G); // right now, need to invalidate since text could be shown
|
|
}
|
|
I->Image = MyPNGRead(fname);
|
|
if(I->Image) {
|
|
if(!quiet) {
|
|
PRINTFB(G, FB_Scene, FB_Details)
|
|
" Scene: loaded image from '%s'.\n", fname ENDFB(G);
|
|
}
|
|
if((stereo > 0) || ((stereo < 0) &&
|
|
(I->Image->getWidth() == 2 * I->Width) &&
|
|
(I->Image->getHeight() == I->Height))) {
|
|
*(I->Image) = I->Image->deinterlace(stereo == 2);
|
|
}
|
|
|
|
I->CopyType = true;
|
|
I->CopyForced = true;
|
|
OrthoRemoveSplash(G);
|
|
SettingSetGlobal_b(G, cSetting_text, 0);
|
|
if(movie_flag &&
|
|
I->Image && !I->Image->empty()) {
|
|
MovieSetImage(G, MovieFrameToImage(G, SettingGetGlobal_i(G, cSetting_frame) - 1)
|
|
, I->Image);
|
|
I->MovieFrameFlag = true;
|
|
} else {
|
|
I->DirtyFlag = false; /* make sure we don't overwrite image */
|
|
}
|
|
OrthoDirty(G);
|
|
ok = true;
|
|
} else {
|
|
if(!quiet) {
|
|
PRINTFB(G, FB_Scene, FB_Errors)
|
|
" Scene: unable to load image from '%s'.\n", fname ENDFB(G);
|
|
}
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
|
|
/*static unsigned int byte_max(unsigned int value)
|
|
{
|
|
return (value>0xFF) ? 0xFF : value;
|
|
}
|
|
*/
|
|
|
|
#define SceneClickMargin DIP2PIXEL(2)
|
|
#define SceneTopMargin 0
|
|
#define SceneToggleMargin DIP2PIXEL(2)
|
|
#define SceneRightMargin 0
|
|
#define SceneToggleWidth DIP2PIXEL(17)
|
|
#define SceneToggleSize DIP2PIXEL(16)
|
|
#define SceneToggleTextShift DIP2PIXEL(4)
|
|
#define SceneTextLeftMargin DIP2PIXEL(1)
|
|
#ifndef _PYMOL_NOPY
|
|
static void draw_button(int x2, int y2, int z, int w, int h, float *light, float *dark,
|
|
float *inside , CGO *orthoCGO)
|
|
{
|
|
if (orthoCGO){
|
|
CGOColorv(orthoCGO, light);
|
|
CGOBegin(orthoCGO, GL_TRIANGLE_STRIP);
|
|
CGOVertex(orthoCGO, x2, y2, z);
|
|
CGOVertex(orthoCGO, x2, y2 + h, z);
|
|
CGOVertex(orthoCGO, x2 + w, y2, z);
|
|
CGOVertex(orthoCGO, x2 + w, y2 + h, z);
|
|
CGOEnd(orthoCGO);
|
|
} else {
|
|
glColor3fv(light);
|
|
glBegin(GL_POLYGON);
|
|
glVertex3i(x2, y2, z);
|
|
glVertex3i(x2, y2 + h, z);
|
|
glVertex3i(x2 + w, y2 + h, z);
|
|
glVertex3i(x2 + w, y2, z);
|
|
glEnd();
|
|
}
|
|
|
|
if (orthoCGO){
|
|
CGOColorv(orthoCGO, dark);
|
|
CGOBegin(orthoCGO, GL_TRIANGLE_STRIP);
|
|
CGOVertex(orthoCGO, x2 + 1, y2, z);
|
|
CGOVertex(orthoCGO, x2 + 1, y2 + h - 1, z);
|
|
CGOVertex(orthoCGO, x2 + w, y2, z);
|
|
CGOVertex(orthoCGO, x2 + w, y2 + h - 1, z);
|
|
CGOEnd(orthoCGO);
|
|
} else {
|
|
glColor3fv(dark);
|
|
glBegin(GL_POLYGON);
|
|
glVertex3i(x2 + 1, y2, z);
|
|
glVertex3i(x2 + 1, y2 + h - 1, z);
|
|
glVertex3i(x2 + w, y2 + h - 1, z);
|
|
glVertex3i(x2 + w, y2, z);
|
|
glEnd();
|
|
}
|
|
|
|
if(inside) {
|
|
if (orthoCGO){
|
|
CGOColorv(orthoCGO, inside);
|
|
CGOBegin(orthoCGO, GL_TRIANGLE_STRIP);
|
|
CGOVertex(orthoCGO, x2 + 1, y2 + 1, z);
|
|
CGOVertex(orthoCGO, x2 + 1, y2 + h - 1, z);
|
|
CGOVertex(orthoCGO, x2 + w - 1, y2 + 1, z);
|
|
CGOVertex(orthoCGO, x2 + w - 1, y2 + h - 1, z);
|
|
CGOEnd(orthoCGO);
|
|
} else {
|
|
glColor3fv(inside);
|
|
glBegin(GL_POLYGON);
|
|
glVertex3i(x2 + 1, y2 + 1, z);
|
|
glVertex3i(x2 + 1, y2 + h - 1, z);
|
|
glVertex3i(x2 + w - 1, y2 + h - 1, z);
|
|
glVertex3i(x2 + w - 1, y2 + 1, z);
|
|
glEnd();
|
|
}
|
|
} else { /* rainbow */
|
|
if (orthoCGO){
|
|
CGOBegin(orthoCGO, GL_TRIANGLE_STRIP);
|
|
CGOColor(orthoCGO, 0.1F, 1.0F, 0.1F); // green
|
|
CGOVertex(orthoCGO, x2 + 1, y2 + h - 1, z);
|
|
CGOColor(orthoCGO, 1.0F, 1.0F, 0.1F); // yellow
|
|
CGOVertex(orthoCGO, x2 + w - 1, y2 + h - 1, z);
|
|
CGOColor(orthoCGO, 1.f, 0.1f, 0.1f); // red
|
|
CGOVertex(orthoCGO, x2 + 1, y2 + 1, z);
|
|
CGOColor(orthoCGO, 0.1F, 0.1F, 1.0F); // blue
|
|
CGOVertex(orthoCGO, x2 + w - 1, y2 + 1, z);
|
|
CGOEnd(orthoCGO);
|
|
} else {
|
|
glBegin(GL_POLYGON);
|
|
glColor3f(1.0F, 0.1F, 0.1F);
|
|
glVertex3i(x2 + 1, y2 + 1, z);
|
|
glColor3f(0.1F, 1.0F, 0.1F);
|
|
glVertex3i(x2 + 1, y2 + h - 1, z);
|
|
glColor3f(1.0F, 1.0F, 0.1F);
|
|
glVertex3i(x2 + w - 1, y2 + h - 1, z);
|
|
glColor3f(0.1F, 0.1F, 1.0F);
|
|
glVertex3i(x2 + w - 1, y2 + 1, z);
|
|
glEnd();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Update the G->Scene->SceneVLA names array which is used for scene buttons
|
|
*
|
|
* @param list List of scene names
|
|
*/
|
|
void SceneSetNames(PyMOLGlobals * G, const std::vector<std::string> &list)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->SceneVec.clear();
|
|
I->SceneVec.reserve(list.size());
|
|
|
|
for (const auto& name : list) {
|
|
I->SceneVec.emplace_back(name, false);
|
|
}
|
|
|
|
OrthoDirty(G);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
static void SceneDrawButtons(Block * block, int draw_for_real , CGO *orthoCGO)
|
|
{
|
|
#ifndef _PYMOL_NOPY
|
|
PyMOLGlobals *G = block->m_G;
|
|
CScene *I = G->Scene;
|
|
int x, y, xx, x2;
|
|
float enabledColor[3] = { 0.5F, 0.5F, 0.5F };
|
|
float pressedColor[3] = { 0.7F, 0.7F, 0.7F };
|
|
float disabledColor[3] = { 0.25F, 0.25F, 0.25F };
|
|
float lightEdge[3] = { 0.6F, 0.6F, 0.6F };
|
|
float darkEdge[3] = { 0.35F, 0.35F, 0.35F };
|
|
int charWidth = DIP2PIXEL(8);
|
|
int n_ent;
|
|
int n_disp;
|
|
int skip = 0;
|
|
int row = -1;
|
|
int lineHeight = DIP2PIXEL(SettingGetGlobal_i(G, cSetting_internal_gui_control_size));
|
|
int text_lift = (lineHeight / 2) - DIP2PIXEL(5);
|
|
int op_cnt = 1;
|
|
|
|
if(((G->HaveGUI && G->ValidContext) || (!draw_for_real)) &&
|
|
((block->rect.right - block->rect.left) > 6) && (!I->SceneVec.empty())) {
|
|
int max_char;
|
|
int nChar;
|
|
I->ButtonsShown = true;
|
|
|
|
/* do we have enough structures to warrant a scroll bar? */
|
|
n_ent = I->SceneVec.size();
|
|
|
|
n_disp =
|
|
(((I->rect.top - I->rect.bottom) - (SceneTopMargin)) / lineHeight) -
|
|
1;
|
|
if(n_disp < 1)
|
|
n_disp = 1;
|
|
|
|
{
|
|
for (auto& elem : I->SceneVec)
|
|
elem.drawn = false;
|
|
}
|
|
if(n_ent > n_disp) {
|
|
int bar_maxed = I->m_ScrollBar.isMaxed();
|
|
if(!I->ScrollBarActive) {
|
|
I->m_ScrollBar.setLimits(n_ent, n_disp);
|
|
if(bar_maxed) {
|
|
I->m_ScrollBar.maxOut();
|
|
I->NSkip = static_cast<int>(I->m_ScrollBar.getValue());
|
|
} else {
|
|
I->m_ScrollBar.setValue(0);
|
|
I->NSkip = 0;
|
|
}
|
|
} else {
|
|
I->m_ScrollBar.setLimits(n_ent, n_disp);
|
|
if(bar_maxed)
|
|
I->m_ScrollBar.maxOut();
|
|
I->NSkip = static_cast<int>(I->m_ScrollBar.getValue());
|
|
}
|
|
I->ScrollBarActive = 1;
|
|
|
|
} else {
|
|
I->ScrollBarActive = 0;
|
|
I->NSkip = 0;
|
|
}
|
|
|
|
max_char = (((I->rect.right - I->rect.left) -
|
|
(SceneTextLeftMargin + SceneRightMargin + 4)) -
|
|
(op_cnt * SceneToggleWidth));
|
|
if(I->ScrollBarActive) {
|
|
max_char -= (SceneScrollBarMargin + SceneScrollBarWidth);
|
|
}
|
|
max_char /= charWidth;
|
|
|
|
if(I->ScrollBarActive) {
|
|
I->m_ScrollBar.setBox(I->rect.top - SceneScrollBarMargin,
|
|
I->rect.left + SceneScrollBarMargin,
|
|
I->rect.bottom + 2,
|
|
I->rect.left + SceneScrollBarMargin + SceneScrollBarWidth);
|
|
if(draw_for_real)
|
|
I->m_ScrollBar.draw(orthoCGO);
|
|
}
|
|
|
|
skip = I->NSkip;
|
|
x = I->rect.left + SceneTextLeftMargin;
|
|
|
|
/* y = ((I->rect.top-lineHeight)-SceneTopMargin)-lineHeight; */
|
|
|
|
{
|
|
int n_vis = n_disp;
|
|
if(n_ent < n_vis)
|
|
n_vis = n_ent;
|
|
y = (I->rect.bottom + SceneBottomMargin) + (n_vis - 1) * lineHeight;
|
|
}
|
|
|
|
/* xx = I->rect.right-SceneRightMargin-SceneToggleWidth*(cRepCnt+op_cnt); */
|
|
xx = I->rect.right - SceneRightMargin - SceneToggleWidth * (op_cnt);
|
|
|
|
if(I->ScrollBarActive) {
|
|
x += SceneScrollBarWidth + SceneScrollBarMargin;
|
|
}
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < n_ent; i++) {
|
|
if(skip) {
|
|
skip--;
|
|
} else {
|
|
row++;
|
|
x2 = xx;
|
|
nChar = max_char;
|
|
|
|
if((x - SceneToggleMargin) - (xx - SceneToggleMargin) > -10) {
|
|
x2 = x + 10;
|
|
}
|
|
{
|
|
float toggleColor[3] = { 0.5F, 0.5F, 1.0F };
|
|
|
|
if(draw_for_real) {
|
|
glColor3fv(toggleColor);
|
|
|
|
TextSetColor(G, I->TextColor);
|
|
TextSetPos2i(G, x + DIP2PIXEL(2), y + text_lift);
|
|
}
|
|
{
|
|
const char *cur_name = SettingGetGlobal_s(G, cSetting_scene_current_name);
|
|
auto elem = &I->SceneVec[i];
|
|
int item = I->NSkip + row;
|
|
auto c = elem->name.c_str();
|
|
int len = static_cast<int>(elem->name.size());
|
|
|
|
x2 = xx;
|
|
if(len > max_char)
|
|
len = max_char;
|
|
x2 = x + len * charWidth + DIP2PIXEL(6);
|
|
|
|
/* store rectangles for finding clicks */
|
|
|
|
elem->drawn = true;
|
|
|
|
elem->rect = pymol::Rect<int>(x, x2, y, y + lineHeight);
|
|
|
|
if(I->ButtonMargin < x2)
|
|
I->ButtonMargin = x2;
|
|
|
|
if(draw_for_real) {
|
|
|
|
if((item == I->Pressed) && (item == I->Over)) {
|
|
draw_button(x, y, 0, (x2 - x) - 1, (lineHeight - 1), lightEdge,
|
|
darkEdge, pressedColor, orthoCGO);
|
|
} else if(cur_name && elem->name == cur_name) {
|
|
draw_button(x, y, 0, (x2 - x) - 1, (lineHeight - 1), lightEdge,
|
|
darkEdge, enabledColor, orthoCGO);
|
|
} else {
|
|
draw_button(x, y, 0, (x2 - x) - 1, (lineHeight - 1), lightEdge,
|
|
darkEdge, disabledColor, orthoCGO);
|
|
}
|
|
|
|
TextSetColor(G, I->TextColor);
|
|
|
|
if(c) {
|
|
while(*c) {
|
|
if((nChar--) > 0)
|
|
TextDrawChar(G, *(c++), orthoCGO);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
y -= lineHeight;
|
|
if(y < (I->rect.bottom))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
I->HowFarDown = y;
|
|
I->ButtonsValid = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// TODO: Replace with ShaderMgr::drawPixelsTo
|
|
static void RendererWritePixelsTo(
|
|
PyMOLGlobals* G, const Rect2D& rect, unsigned char* buffer)
|
|
{
|
|
#ifndef PURE_OPENGL_ES_2
|
|
glRasterPos3i(rect.offset.x, rect.offset.y, -10);
|
|
#endif
|
|
PyMOLDrawPixels(rect.extent.width, rect.extent.height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
|
|
}
|
|
|
|
static bool SceneOverlayOversize(
|
|
PyMOLGlobals* G, unsigned char* data, CGO* orthoCGO)
|
|
{
|
|
bool drawn = false;
|
|
auto I = G->Scene;
|
|
auto show_alpha = SettingGet<bool>(G, cSetting_show_alpha_checker);
|
|
const float* bg_color =
|
|
ColorGet(G, SettingGet<int>(G, nullptr, nullptr, cSetting_bg_rgb));
|
|
unsigned int bg_rr, bg_r = (unsigned int) (255 * bg_color[0]);
|
|
unsigned int bg_gg, bg_g = (unsigned int) (255 * bg_color[1]);
|
|
unsigned int bg_bb, bg_b = (unsigned int) (255 * bg_color[2]);
|
|
|
|
int factor = 1;
|
|
int shift = 0;
|
|
int tmp_height = I->Image->getHeight();
|
|
int tmp_width = I->Image->getWidth();
|
|
int src_row_bytes = I->Image->getWidth() * pymol::Image::getPixelSize();
|
|
unsigned int color_word;
|
|
float rgba[4] = {0.0F, 0.0F, 0.0F, 1.0F};
|
|
|
|
ColorGetBkrdContColor(G, rgba, false);
|
|
color_word = ColorGet32BitWord(G, rgba);
|
|
|
|
while (tmp_height && tmp_width &&
|
|
((tmp_height > (I->Height - 3)) || (tmp_width > (I->Width - 3)))) {
|
|
tmp_height = (tmp_height >> 1);
|
|
tmp_width = (tmp_width >> 1);
|
|
factor = (factor << 1);
|
|
shift++;
|
|
}
|
|
tmp_width += 2;
|
|
tmp_height += 2;
|
|
|
|
if (tmp_height && tmp_width) {
|
|
unsigned int buffer_size = tmp_height * tmp_width * 4;
|
|
std::vector<unsigned char> buffer_vec(buffer_size);
|
|
auto* buffer = buffer_vec.data();
|
|
|
|
if (!buffer_vec.empty() && data) {
|
|
unsigned char* p = data;
|
|
unsigned char* q = buffer;
|
|
unsigned char *pp, *ppp, *pppp;
|
|
unsigned int c1, c2, c3, c4, tot, bg;
|
|
unsigned int factor_col_bytes = factor * 4;
|
|
unsigned int factor_row_bytes = factor * src_row_bytes;
|
|
|
|
shift = shift + shift;
|
|
|
|
for (int a = 0; a < tmp_width; a++) { /* border, first row */
|
|
*((unsigned int*) (q)) = color_word;
|
|
q += 4;
|
|
}
|
|
for (int b = 1; b < tmp_height - 1; b++) { /* rows */
|
|
pp = p;
|
|
*((unsigned int*) (q)) = color_word; /* border */
|
|
q += 4;
|
|
for (int a = 1; a < tmp_width - 1; a++) { /* cols */
|
|
ppp = pp;
|
|
|
|
c1 = c2 = c3 = c4 = tot = 0;
|
|
|
|
if (show_alpha &&
|
|
(((a >> 4) + (b >> 4)) & 0x1)) { /* introduce checkerboard */
|
|
bg_rr = ((bg_r & 0x80) ? bg_r - TRN_BKG : bg_r + TRN_BKG);
|
|
bg_gg = ((bg_g & 0x80) ? bg_g - TRN_BKG : bg_g + TRN_BKG);
|
|
bg_bb = ((bg_b & 0x80) ? bg_b - TRN_BKG : bg_b + TRN_BKG);
|
|
} else {
|
|
bg_rr = bg_r;
|
|
bg_gg = bg_g;
|
|
bg_bb = bg_b;
|
|
}
|
|
|
|
for (int d = 0; d < factor; d++) { /* box rows */
|
|
pppp = ppp;
|
|
for (int c = 0; c < factor; c++) { /* box cols */
|
|
unsigned char alpha = pppp[3];
|
|
c1 += *(pppp++) * alpha;
|
|
c2 += *(pppp++) * alpha;
|
|
c3 += *(pppp++) * alpha;
|
|
pppp++;
|
|
c4 += alpha;
|
|
tot += 0xFF;
|
|
}
|
|
ppp += src_row_bytes;
|
|
}
|
|
if (c4) {
|
|
bg = tot - c4;
|
|
*(q++) = (c1 + bg_rr * bg) / tot;
|
|
*(q++) = (c2 + bg_gg * bg) / tot;
|
|
*(q++) = (c3 + bg_bb * bg) / tot;
|
|
*(q++) = 0xFF;
|
|
} else {
|
|
*(q++) = bg_rr;
|
|
*(q++) = bg_gg;
|
|
*(q++) = bg_bb;
|
|
*(q++) = 0xFF;
|
|
}
|
|
pp += factor_col_bytes;
|
|
}
|
|
*((unsigned int*) (q)) = color_word; /* border */
|
|
q += 4;
|
|
p += factor_row_bytes;
|
|
}
|
|
for (int a = 0; a < tmp_width; a++) { /* border, last row */
|
|
*((unsigned int*) (q)) = color_word;
|
|
q += 4;
|
|
}
|
|
Rect2D rect{{(int) ((I->Width - tmp_width) / 2 + I->rect.left),
|
|
(int) ((I->Height - tmp_height) / 2 + I->rect.bottom)},
|
|
{tmp_width, tmp_height}};
|
|
RendererWritePixelsTo(G, rect, buffer);
|
|
drawn = true;
|
|
}
|
|
}
|
|
int text_pos = (I->Height - tmp_height) / 2 - 15;
|
|
int x_pos, y_pos;
|
|
if (text_pos < 0) {
|
|
text_pos = (I->Height - tmp_height) / 2 + 3;
|
|
x_pos = (I->Width - tmp_width) / 2 + 3;
|
|
y_pos = text_pos;
|
|
} else {
|
|
x_pos = (I->Width - tmp_width) / 2;
|
|
y_pos = text_pos;
|
|
}
|
|
|
|
auto buffer = pymol::join_to_string(
|
|
"Image size = ", I->Image->getWidth(), " x ", I->Image->getHeight());
|
|
|
|
TextSetColor3f(G, rgba[0], rgba[1], rgba[2]);
|
|
TextDrawStrAt(G, buffer.c_str(), x_pos + I->rect.left,
|
|
y_pos + I->rect.bottom, orthoCGO);
|
|
return drawn;
|
|
}
|
|
|
|
static bool SceneOverlayOversizeBorder(
|
|
PyMOLGlobals* G, int width, int height, unsigned char* data)
|
|
{
|
|
/* but a border around image */
|
|
auto I = G->Scene;
|
|
bool drawn = false;
|
|
|
|
auto show_alpha = SettingGet<bool>(G, cSetting_show_alpha_checker);
|
|
const float* bg_color =
|
|
ColorGet(G, SettingGet<int>(G, nullptr, nullptr, cSetting_bg_rgb));
|
|
unsigned int bg_rr, bg_r = (unsigned int) (255 * bg_color[0]);
|
|
unsigned int bg_gg, bg_g = (unsigned int) (255 * bg_color[1]);
|
|
unsigned int bg_bb, bg_b = (unsigned int) (255 * bg_color[2]);
|
|
|
|
unsigned int color_word;
|
|
float rgba[4] = {0.0F, 0.0F, 0.0F, 1.0F};
|
|
unsigned int tmp_height = height + 2;
|
|
unsigned int tmp_width = width + 2;
|
|
unsigned int border = 1;
|
|
unsigned int upscale = 1;
|
|
|
|
// Upscale for Retina/4K
|
|
if (DIP2PIXEL(height) == I->Height && DIP2PIXEL(width) == I->Width) {
|
|
upscale = DIP2PIXEL(1);
|
|
tmp_height = DIP2PIXEL(height);
|
|
tmp_width = DIP2PIXEL(width);
|
|
border = 0;
|
|
}
|
|
|
|
unsigned int n_word = tmp_height * tmp_width;
|
|
std::vector<unsigned int> tmp_buffer_vec(n_word);
|
|
ColorGetBkrdContColor(G, rgba, false);
|
|
color_word = ColorGet32BitWord(G, rgba);
|
|
|
|
if (!tmp_buffer_vec.empty()) {
|
|
auto* tmp_buffer = tmp_buffer_vec.data();
|
|
unsigned int a, b;
|
|
unsigned int* p = (unsigned int*) data;
|
|
unsigned int* q = tmp_buffer;
|
|
|
|
// top border
|
|
for (a = 0; a < border; ++a) {
|
|
for (b = 0; b < tmp_width; b++)
|
|
*(q++) = color_word;
|
|
}
|
|
|
|
for (a = border; a < tmp_height - border; a++) {
|
|
// left border
|
|
for (b = 0; b < border; ++b) {
|
|
*(q++) = color_word;
|
|
}
|
|
|
|
for (b = border; b < tmp_width - border; b++) {
|
|
unsigned char* qq = (unsigned char*) q;
|
|
unsigned char* pp = (unsigned char*) p;
|
|
unsigned char bg;
|
|
if (show_alpha &&
|
|
(((a >> 4) + (b >> 4)) & 0x1)) { /* introduce checkerboard */
|
|
bg_rr = ((bg_r & 0x80) ? bg_r - TRN_BKG : bg_r + TRN_BKG);
|
|
bg_gg = ((bg_g & 0x80) ? bg_g - TRN_BKG : bg_g + TRN_BKG);
|
|
bg_bb = ((bg_b & 0x80) ? bg_b - TRN_BKG : bg_b + TRN_BKG);
|
|
} else {
|
|
bg_rr = bg_r;
|
|
bg_gg = bg_g;
|
|
bg_bb = bg_b;
|
|
}
|
|
if (pp[3]) {
|
|
bg = 0xFF - pp[3];
|
|
*(qq++) = (pp[0] * pp[3] + bg_rr * bg) / 0xFF;
|
|
*(qq++) = (pp[1] * pp[3] + bg_gg * bg) / 0xFF;
|
|
*(qq++) = (pp[2] * pp[3] + bg_bb * bg) / 0xFF;
|
|
*(qq++) = 0xFF;
|
|
} else {
|
|
*(qq++) = bg_rr;
|
|
*(qq++) = bg_gg;
|
|
*(qq++) = bg_bb;
|
|
*(qq++) = 0xFF;
|
|
}
|
|
q++;
|
|
|
|
if ((b + 1 - border) % upscale == 0) {
|
|
p++;
|
|
}
|
|
}
|
|
|
|
if ((a + 1 - border) % upscale != 0) {
|
|
// read row again
|
|
p -= width;
|
|
}
|
|
|
|
// right border
|
|
for (b = 0; b < border; ++b) {
|
|
*(q++) = color_word;
|
|
}
|
|
}
|
|
|
|
// bottom border
|
|
for (a = 0; a < border; ++a) {
|
|
for (b = 0; b < tmp_width; b++)
|
|
*(q++) = color_word;
|
|
}
|
|
|
|
Rect2D rect{{(int) ((I->Width - tmp_width) / 2 + I->rect.left),
|
|
(int) ((I->Height - tmp_height) / 2 + I->rect.bottom)},
|
|
{tmp_width, tmp_height}};
|
|
RendererWritePixelsTo(G, rect, (unsigned char*) tmp_buffer);
|
|
drawn = true;
|
|
}
|
|
return drawn;
|
|
}
|
|
|
|
static bool SceneOverlayExactFitNoAlpha(PyMOLGlobals* G, int width, int height, unsigned char* data)
|
|
{
|
|
auto I = G->Scene;
|
|
Rect2D rect{{(int) ((I->Width - width) / 2 + I->rect.left),
|
|
(int) ((I->Height - height) / 2 + I->rect.bottom)},
|
|
{width, height}};
|
|
RendererWritePixelsTo(G, rect, data);
|
|
return true;
|
|
}
|
|
|
|
static bool SceneOverlayExactFit(PyMOLGlobals* G, int width, int height, unsigned char* data)
|
|
{
|
|
auto I = G->Scene;
|
|
float rgba[4] = {0.0F, 0.0F, 0.0F, 1.0F};
|
|
unsigned int n_word = height * width;
|
|
std::vector<unsigned int> tmp_buffer_vec(n_word);
|
|
ColorGetBkrdContColor(G, rgba, false);
|
|
|
|
auto show_alpha = SettingGet<bool>(G, cSetting_show_alpha_checker);
|
|
const float* bg_color =
|
|
ColorGet(G, SettingGet<int>(G, nullptr, nullptr, cSetting_bg_rgb));
|
|
unsigned int bg_rr, bg_r = (unsigned int) (255 * bg_color[0]);
|
|
unsigned int bg_gg, bg_g = (unsigned int) (255 * bg_color[1]);
|
|
unsigned int bg_bb, bg_b = (unsigned int) (255 * bg_color[2]);
|
|
|
|
if (tmp_buffer_vec.empty()) {
|
|
return false;
|
|
}
|
|
auto* tmp_buffer = tmp_buffer_vec.data();
|
|
unsigned int a, b;
|
|
unsigned int* p = (unsigned int*) data;
|
|
unsigned int* q = tmp_buffer;
|
|
for (a = 0; a < (unsigned int) height; a++) {
|
|
for (b = 0; b < (unsigned int) width; b++) {
|
|
unsigned char* qq = (unsigned char*) q;
|
|
unsigned char* pp = (unsigned char*) p;
|
|
unsigned char bg;
|
|
if (show_alpha &&
|
|
(((a >> 4) + (b >> 4)) & 0x1)) { /* introduce checkerboard */
|
|
bg_rr = ((bg_r & 0x80) ? bg_r - TRN_BKG : bg_r + TRN_BKG);
|
|
bg_gg = ((bg_g & 0x80) ? bg_g - TRN_BKG : bg_g + TRN_BKG);
|
|
bg_bb = ((bg_b & 0x80) ? bg_b - TRN_BKG : bg_b + TRN_BKG);
|
|
} else {
|
|
bg_rr = bg_r;
|
|
bg_gg = bg_g;
|
|
bg_bb = bg_b;
|
|
}
|
|
if (pp[3]) {
|
|
bg = 0xFF - pp[3];
|
|
*(qq++) = (pp[0] * pp[3] + bg_rr * bg) / 0xFF;
|
|
*(qq++) = (pp[1] * pp[3] + bg_gg * bg) / 0xFF;
|
|
*(qq++) = (pp[2] * pp[3] + bg_bb * bg) / 0xFF;
|
|
*(qq++) = 0xFF;
|
|
} else {
|
|
*(qq++) = bg_rr;
|
|
*(qq++) = bg_gg;
|
|
*(qq++) = bg_bb;
|
|
*(qq++) = 0xFF;
|
|
}
|
|
q++;
|
|
p++;
|
|
}
|
|
}
|
|
Rect2D rect{{(int) ((I->Width - width) / 2 + I->rect.left),
|
|
(int) ((I->Height - height) / 2 + I->rect.bottom)},
|
|
{width, height}};
|
|
RendererWritePixelsTo(G, rect, (unsigned char*) tmp_buffer);
|
|
return true;
|
|
}
|
|
|
|
int SceneDrawImageOverlay(PyMOLGlobals* G, int override, CGO* orthoCGO)
|
|
{
|
|
CScene* I = G->Scene;
|
|
int drawn = false;
|
|
int text = SettingGet<bool>(G, cSetting_text);
|
|
/* is the text/overlay (ESC) on? */
|
|
int overlay = OrthoGetOverlayStatus(G);
|
|
bool draw_overlay = (!text || overlay) && (override || I->CopyType == true) &&
|
|
I->Image && !I->Image->empty();
|
|
|
|
if (!draw_overlay) {
|
|
return drawn;
|
|
}
|
|
|
|
int width = I->Image->getWidth();
|
|
int height = I->Image->getHeight();
|
|
unsigned char* data = I->Image->bits();
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if (I->Image->isStereo()) {
|
|
int buffer;
|
|
glGetIntegerv(GL_DRAW_BUFFER, (GLint*) &buffer);
|
|
if (buffer == GL_BACK_RIGHT) /* hardware stereo */
|
|
data += I->Image->getSizeInBytes();
|
|
else {
|
|
int stereo = SettingGetGlobal_i(G, cSetting_stereo);
|
|
if (stereo) {
|
|
switch (OrthoGetRenderMode(G)) {
|
|
case OrthoRenderMode::GeoWallRight:
|
|
data += I->Image->getSizeInBytes();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ((height > I->Height) || (width > I->Width)) { /* image is oversize */
|
|
drawn = SceneOverlayOversize(G, data, orthoCGO);
|
|
} else if (((width < I->Width) || (height < I->Height)) &&
|
|
((I->Width - width) > 2) && ((I->Height - height) > 2)) {
|
|
drawn = SceneOverlayOversizeBorder(G, width, height, data);
|
|
} else if (I->CopyForced) { /* near-exact fit */
|
|
drawn = SceneOverlayExactFit(G, width, height, data);
|
|
} else { /* not a forced copy, so don't show/blend alpha */
|
|
drawn = SceneOverlayExactFitNoAlpha(G, width, height, data);
|
|
}
|
|
|
|
I->LastRender = UtilGetSeconds(G);
|
|
return drawn;
|
|
}
|
|
|
|
void CScene::draw(CGO* orthoCGO) /* returns true if scene was drawn (using a cached image) */
|
|
{
|
|
PyMOLGlobals *G = m_G;
|
|
CScene *I = G->Scene;
|
|
int drawn = false;
|
|
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
|
|
I->ButtonsShown = false;
|
|
|
|
drawn = SceneDrawImageOverlay(G, 0, orthoCGO);
|
|
|
|
if(SettingGetGlobal_b(G, cSetting_scene_buttons)) {
|
|
SceneDrawButtons(this, true, orthoCGO);
|
|
} else {
|
|
I->ButtonMargin = 0;
|
|
}
|
|
}
|
|
if(drawn)
|
|
OrthoDrawWizardPrompt(G, orthoCGO); /* ugly hack necessitated because wizard
|
|
prompt is overwritten when image is drawn */
|
|
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneDoRoving(PyMOLGlobals * G, float old_front,
|
|
float old_back, float old_origin,
|
|
int adjust_flag, int zoom_flag)
|
|
{
|
|
EditorFavorOrigin(G, nullptr);
|
|
if(SettingGetGlobal_b(G, cSetting_roving_origin)) {
|
|
|
|
CScene *I = G->Scene;
|
|
float delta_front, delta_back;
|
|
float front_weight, back_weight, slab_width;
|
|
float z_buffer = 3.0;
|
|
float old_pos2 = 0.0F;
|
|
float v2[3];
|
|
|
|
z_buffer = SettingGetGlobal_f(G, cSetting_roving_origin_z_cushion);
|
|
|
|
delta_front = I->m_view.m_clip().m_front - old_front;
|
|
delta_back = I->m_view.m_clip().m_back - old_back;
|
|
|
|
zero3f(v2);
|
|
|
|
slab_width = I->m_view.m_clip().m_back - I->m_view.m_clip().m_front;
|
|
|
|
/* first, check to make sure that the origin isn't too close to either plane */
|
|
if((z_buffer * 2) > slab_width)
|
|
z_buffer = slab_width * 0.5F;
|
|
|
|
if(old_origin < (I->m_view.m_clip().m_front + z_buffer)) { /* old origin behind front plane */
|
|
front_weight = 1.0F;
|
|
delta_front = (I->m_view.m_clip().m_front + z_buffer) - old_origin; /* move origin into allowed regioin */
|
|
} else if(old_origin > (I->m_view.m_clip().m_back - z_buffer)) { /* old origin was behind back plane */
|
|
front_weight = 0.0F;
|
|
delta_back = (I->m_view.m_clip().m_back - z_buffer) - old_origin;
|
|
|
|
} else if(slab_width >= R_SMALL4) { /* otherwise, if slab exists */
|
|
front_weight = (old_back - old_origin) / slab_width; /* weight based on relative proximity */
|
|
} else {
|
|
front_weight = 0.5F;
|
|
}
|
|
|
|
back_weight = 1.0F - front_weight;
|
|
|
|
if((front_weight > 0.2) && (back_weight > 0.2)) { /* origin not near edge */
|
|
if(delta_front * delta_back > 0.0F) { /* planes moving in same direction */
|
|
if(fabs(delta_front) > fabs(delta_back)) { /* so stick with whichever moves less */
|
|
v2[2] = delta_back;
|
|
} else {
|
|
v2[2] = delta_front;
|
|
}
|
|
} else {
|
|
/* planes moving in opposite directions (increasing slab size) */
|
|
/* don't move origin */
|
|
}
|
|
} else { /* origin is near edge -- move origin with plane having highest weight */
|
|
if(front_weight < back_weight) {
|
|
v2[2] = delta_back;
|
|
} else {
|
|
v2[2] = delta_front;
|
|
}
|
|
}
|
|
|
|
old_pos2 = I->m_view.pos().z;
|
|
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), v2, v2); /* transform offset into realspace */
|
|
subtract3f(glm::value_ptr(I->m_view.origin()), v2, v2); /* calculate new origin location */
|
|
SceneOriginSet(G, v2, true); /* move origin, preserving camera location */
|
|
|
|
if(SettingGetGlobal_b(G, cSetting_ortho) || zoom_flag) {
|
|
/* we're orthoscopic, so we don't want the effective field of view
|
|
to change. Thus, we have to hold Pos[2] constant, and instead
|
|
move the planes.
|
|
*/
|
|
float delta = old_pos2 - I->m_view.pos().z;
|
|
I->m_view.translate(0, 0, delta);
|
|
SceneClipSet(G, I->m_view.m_clip().m_front - delta, I->m_view.m_clip().m_back - delta);
|
|
}
|
|
slab_width = I->m_view.m_clip().m_back - I->m_view.m_clip().m_front;
|
|
|
|
/* first, check to make sure that the origin isn't too close to either plane */
|
|
if((z_buffer * 2) > slab_width)
|
|
z_buffer = slab_width * 0.5F;
|
|
|
|
}
|
|
if((adjust_flag) && SettingGetGlobal_b(G, cSetting_roving_detail)) {
|
|
SceneRovingPostpone(G);
|
|
}
|
|
if(SettingGetGlobal_b(G, cSetting_roving_detail)) {
|
|
SceneRovingDirty(G);
|
|
}
|
|
}
|
|
|
|
float ScenePushRasterMatrix(PyMOLGlobals * G, float *v)
|
|
{
|
|
float scale = SceneGetExactScreenVertexScale(G, v);
|
|
#ifndef PURE_OPENGL_ES_2
|
|
CScene *I = G->Scene;
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glTranslatef(v[0], v[1], v[2]); /* go to this position */
|
|
glMultMatrixf(I->InvMatrix);
|
|
glScalef(scale, scale, scale);
|
|
#endif
|
|
return scale;
|
|
}
|
|
|
|
void ScenePopRasterMatrix(PyMOLGlobals * G)
|
|
{
|
|
#ifdef PURE_OPENGL_ES_2
|
|
/* TODO */
|
|
#else
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPopMatrix();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Compose the ModelViewMatrix from Pos, RotMatrix and Origin
|
|
* See also: CScene.ModMatrix (queried from OpenGL)
|
|
*
|
|
* @param[out] modelView 4x4 matrix
|
|
*/
|
|
static void SceneComposeModelViewMatrix(CScene * I, float * modelView) {
|
|
identity44f(modelView);
|
|
const auto& pos = I->m_view.pos();
|
|
const auto& ori = I->m_view.origin();
|
|
MatrixTranslateC44f(modelView, pos.x, pos.y, pos.z);
|
|
MatrixMultiplyC44f(glm::value_ptr(I->m_view.rotMatrix()), modelView);
|
|
MatrixTranslateC44f(modelView, -ori.x, -ori.y, -ori.z);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneGetEyeNormal(PyMOLGlobals * G, float *v1, float *normal)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float p1[4], p2[4];
|
|
float modelView[16];
|
|
|
|
SceneComposeModelViewMatrix(I, modelView);
|
|
|
|
copy3f(v1, p1);
|
|
p1[3] = 1.0;
|
|
MatrixTransformC44f4f(modelView, p1, p2); /* modelview transformation */
|
|
copy3f(p2, p1);
|
|
normalize3f(p1);
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), p1, p2);
|
|
invert3f3f(p2, normal);
|
|
}
|
|
|
|
/**
|
|
* Return true if the v1 is within the safe clipping planes
|
|
*/
|
|
bool SceneGetVisible(PyMOLGlobals * G, const float *v1)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float depth = SceneGetRawDepth(G, v1);
|
|
return (I->m_view.m_clipSafe().m_back >= depth && depth >= I->m_view.m_clipSafe().m_front);
|
|
}
|
|
|
|
/**
|
|
* Get the depth (camera space Z) of v1
|
|
*
|
|
* @param v1 point (3f) in world space or nullptr (= origin)
|
|
*/
|
|
float SceneGetRawDepth(PyMOLGlobals * G, const float *v1)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float vt[3];
|
|
float modelView[16];
|
|
|
|
if(!v1 || SettingGetGlobal_i(G, cSetting_ortho))
|
|
return -I->m_view.pos().z;
|
|
|
|
SceneComposeModelViewMatrix(I, modelView);
|
|
|
|
MatrixTransformC44f3f(modelView, v1, vt);
|
|
return -vt[2];
|
|
}
|
|
|
|
/**
|
|
* Get the depth (camera space Z) of v1 in normalized clip space
|
|
* from 0.0 (near) to 1.0 (far)
|
|
*
|
|
* @param v1 point (3f) in world space or nullptr (= origin)
|
|
*/
|
|
float SceneGetDepth(PyMOLGlobals * G, const float *v1)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float rawDepth = SceneGetRawDepth(G, v1);
|
|
return ((rawDepth - I->m_view.m_clipSafe().m_front)/(I->m_view.m_clipSafe().m_back-I->m_view.m_clipSafe().m_front));
|
|
}
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
* Get the angstrom per pixel factor at v1. If v1 is nullptr, return the
|
|
* factor at the origin, but clamped to an empirical positive value.
|
|
*
|
|
* @param v1 point (3f) in world space or nullptr (= origin)
|
|
*/
|
|
float SceneGetScreenVertexScale(PyMOLGlobals * G, const float *v1)
|
|
|
|
/* does not require OpenGL-provided matrices */
|
|
{
|
|
float depth = SceneGetRawDepth(G, v1);
|
|
float ratio = depth * GetFovWidth(G) / G->Scene->Height;
|
|
|
|
if(!v1 && ratio < R_SMALL4)
|
|
// origin depth, return a safe clipped value (origin must not be
|
|
// behind or very close in front of the camera)
|
|
ratio = R_SMALL4;
|
|
|
|
return ratio;
|
|
}
|
|
|
|
void SceneRovingChanged(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
SceneRovingDirty(G);
|
|
I->RovingCleanupFlag = true;
|
|
}
|
|
|
|
static void SceneRovingCleanup(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
const char *s;
|
|
char buffer[OrthoLineLength];
|
|
|
|
I->RovingCleanupFlag = false;
|
|
|
|
s = SettingGet_s(G, nullptr, nullptr, cSetting_roving_selection);
|
|
|
|
sprintf(buffer, "cmd.hide('lines','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
sprintf(buffer, "cmd.hide('sticks','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
sprintf(buffer, "cmd.hide('spheres','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
sprintf(buffer, "cmd.hide('ribbon','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
sprintf(buffer, "cmd.hide('cartoon','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
sprintf(buffer, "cmd.hide('labels','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
sprintf(buffer, "cmd.hide('nonbonded','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
sprintf(buffer, "cmd.hide('nb_spheres','''%s''')", s);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
}
|
|
|
|
void SceneRovingUpdate(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
char buffer[OrthoLineLength];
|
|
float sticks, lines, spheres, labels, ribbon, cartoon;
|
|
float polar_contacts, polar_cutoff, nonbonded, nb_spheres;
|
|
char byres[10] = "byres";
|
|
char not_[4] = "not";
|
|
char empty[1] = "";
|
|
char *p1;
|
|
char *p2;
|
|
const char *s;
|
|
int refresh_flag = false;
|
|
const char *name;
|
|
float level;
|
|
float isosurface, isomesh;
|
|
|
|
if(I->RovingDirtyFlag && ((UtilGetSeconds(G) - I->RovingLastUpdate) >
|
|
fabs(SettingGetGlobal_f(G, cSetting_roving_delay)))) {
|
|
|
|
if(I->RovingCleanupFlag)
|
|
SceneRovingCleanup(G);
|
|
|
|
s = SettingGet_s(G, nullptr, nullptr, cSetting_roving_selection);
|
|
sticks = SettingGetGlobal_f(G, cSetting_roving_sticks);
|
|
lines = SettingGetGlobal_f(G, cSetting_roving_lines);
|
|
labels = SettingGetGlobal_f(G, cSetting_roving_labels);
|
|
spheres = SettingGetGlobal_f(G, cSetting_roving_spheres);
|
|
ribbon = SettingGetGlobal_f(G, cSetting_roving_ribbon);
|
|
cartoon = SettingGetGlobal_f(G, cSetting_roving_cartoon);
|
|
polar_contacts = SettingGetGlobal_f(G, cSetting_roving_polar_contacts);
|
|
polar_cutoff = SettingGetGlobal_f(G, cSetting_roving_polar_cutoff);
|
|
nonbonded = SettingGetGlobal_f(G, cSetting_roving_nonbonded);
|
|
nb_spheres = SettingGetGlobal_f(G, cSetting_roving_nb_spheres);
|
|
|
|
isomesh = SettingGetGlobal_f(G, cSetting_roving_isomesh);
|
|
isosurface = SettingGetGlobal_f(G, cSetting_roving_isosurface);
|
|
|
|
if(SettingGetGlobal_b(G, cSetting_roving_byres))
|
|
p2 = byres;
|
|
else
|
|
p2 = empty;
|
|
|
|
if(sticks != 0.0F) {
|
|
if(sticks < 0.0F) {
|
|
p1 = not_;
|
|
sticks = (float) fabs(sticks);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('sticks','''%s''');cmd.show('sticks','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, sticks);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(lines != 0.0F) {
|
|
if(lines < 0.0F) {
|
|
p1 = not_;
|
|
lines = (float) fabs(lines);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('lines','''%s''');cmd.show('lines','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, lines);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(labels != 0.0F) {
|
|
if(labels < 0.0F) {
|
|
p1 = not_;
|
|
labels = (float) fabs(labels);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('labels','''%s''');cmd.show('labels','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, labels);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(spheres != 0.0F) {
|
|
if(spheres < 0.0F) {
|
|
p1 = not_;
|
|
spheres = (float) fabs(spheres);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('spheres','''%s''');cmd.show('spheres','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, spheres);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(cartoon != 0.0F) {
|
|
if(cartoon < 0.0F) {
|
|
p1 = not_;
|
|
cartoon = (float) fabs(cartoon);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('cartoon','''%s''');cmd.show('cartoon','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, cartoon);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(ribbon != 0.0F) {
|
|
if(ribbon < 0.0F) {
|
|
p1 = not_;
|
|
ribbon = (float) fabs(ribbon);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('ribbon','''%s''');cmd.show('ribbon','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, ribbon);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(polar_contacts != 0.0F) {
|
|
int label_flag = 0;
|
|
if(polar_contacts < 0.0F) {
|
|
p1 = not_;
|
|
polar_contacts = (float) fabs(polar_contacts);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
if(polar_cutoff < 0.0F) {
|
|
label_flag = true;
|
|
polar_cutoff = (float) fabs(polar_cutoff);
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.delete('rov_pc');cmd.dist('rov_pc','%s & enabled & %s %s (center expand %1.3f)','same',%1.4f,mode=2,label=%d,quiet=2)",
|
|
s, p1, p2, polar_contacts, polar_cutoff, label_flag);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(nonbonded != 0.0F) {
|
|
if(nonbonded < 0.0F) {
|
|
p1 = not_;
|
|
nonbonded = (float) fabs(nonbonded);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('nonbonded','''%s''');cmd.show('nonbonded','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, nonbonded);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(nb_spheres != 0.0F) {
|
|
if(nb_spheres < 0.0F) {
|
|
p1 = not_;
|
|
nb_spheres = (float) fabs(nb_spheres);
|
|
} else {
|
|
p1 = empty;
|
|
}
|
|
sprintf(buffer,
|
|
"cmd.hide('nb_spheres','''%s''');cmd.show('nb_spheres','%s & enabled & %s %s (center expand %1.3f)')",
|
|
s, s, p1, p2, nb_spheres);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
if(isomesh != 0.0F) {
|
|
int auto_save;
|
|
|
|
auto_save = SettingGetGlobal_i(G, cSetting_auto_zoom);
|
|
SettingSetGlobal_i(G, cSetting_auto_zoom, 0);
|
|
|
|
name = SettingGet_s(G, nullptr, nullptr, cSetting_roving_map1_name);
|
|
if(name)
|
|
if(name[0])
|
|
if(ExecutiveFindObjectByName(G, name)) {
|
|
level = SettingGetGlobal_f(G, cSetting_roving_map1_level);
|
|
sprintf(buffer,
|
|
"cmd.isomesh('rov_m1','%s',%8.6f,'center',%1.3f)",
|
|
name, level, isomesh);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
name = SettingGet_s(G, nullptr, nullptr, cSetting_roving_map2_name);
|
|
if(name)
|
|
if(name[0])
|
|
if(ExecutiveFindObjectByName(G, name)) {
|
|
level = SettingGetGlobal_f(G, cSetting_roving_map2_level);
|
|
sprintf(buffer,
|
|
"cmd.isomesh('rov_m2','%s',%8.6f,'center',%1.3f)",
|
|
name, level, isomesh);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
name = SettingGet_s(G, nullptr, nullptr, cSetting_roving_map3_name);
|
|
if(name)
|
|
if(name[0])
|
|
if(ExecutiveFindObjectByName(G, name)) {
|
|
level = SettingGetGlobal_f(G, cSetting_roving_map3_level);
|
|
sprintf(buffer,
|
|
"cmd.isomesh('rov_m3','%s',%8.6f,'center',%1.3f)",
|
|
name, level, isomesh);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
SettingSetGlobal_i(G, cSetting_auto_zoom, auto_save);
|
|
}
|
|
|
|
if(isosurface != 0.0F) {
|
|
int auto_save;
|
|
|
|
auto_save = SettingGetGlobal_i(G, cSetting_auto_zoom);
|
|
SettingSetGlobal_i(G, cSetting_auto_zoom, 0);
|
|
|
|
name = SettingGet_s(G, nullptr, nullptr, cSetting_roving_map1_name);
|
|
if(name)
|
|
if(name[0])
|
|
if(ExecutiveFindObjectByName(G, name)) {
|
|
level = SettingGetGlobal_f(G, cSetting_roving_map1_level);
|
|
sprintf(buffer,
|
|
"cmd.isosurface('rov_s1','%s',%8.6f,'center',%1.3f)",
|
|
name, level, isosurface);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
name = SettingGet_s(G, nullptr, nullptr, cSetting_roving_map2_name);
|
|
if(name)
|
|
if(name[0])
|
|
if(ExecutiveFindObjectByName(G, name)) {
|
|
level = SettingGetGlobal_f(G, cSetting_roving_map2_level);
|
|
sprintf(buffer,
|
|
"cmd.isosurface('rov_s2','%s',%8.6f,'center',%1.3f)",
|
|
name, level, isosurface);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
|
|
name = SettingGet_s(G, nullptr, nullptr, cSetting_roving_map3_name);
|
|
if(name)
|
|
if(name[0])
|
|
if(ExecutiveFindObjectByName(G, name)) {
|
|
level = SettingGetGlobal_f(G, cSetting_roving_map3_level);
|
|
sprintf(buffer,
|
|
"cmd.isosurface('rov_s3','%s',%8.6f,'center',%1.3f)",
|
|
name, level, isosurface);
|
|
PParse(G, buffer);
|
|
PFlush(G);
|
|
refresh_flag = true;
|
|
}
|
|
SettingSetGlobal_i(G, cSetting_auto_zoom, auto_save);
|
|
}
|
|
|
|
if(refresh_flag) {
|
|
PParse(G, "cmd.refresh()");
|
|
PFlush(G);
|
|
}
|
|
|
|
I->RovingLastUpdate = UtilGetSeconds(G);
|
|
I->RovingDirtyFlag = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Will call cmd.raw_image_callback(img) with the current RGBA image, copied
|
|
* to a WxHx4 numpy array. Return false if no callback is defined
|
|
* (cmd.raw_image_callback == None).
|
|
*/
|
|
static
|
|
bool call_raw_image_callback(PyMOLGlobals * G) {
|
|
bool done = false;
|
|
|
|
#ifndef _PYMOL_NOPY
|
|
int blocked = PAutoBlock(G);
|
|
auto raw_image_callback =
|
|
PyObject_GetAttrString(G->P_inst->cmd, "raw_image_callback");
|
|
|
|
if (raw_image_callback != Py_None) {
|
|
#ifdef _PYMOL_NUMPY
|
|
auto& image = G->Scene->Image;
|
|
|
|
// RGBA image as uint8 numpy array
|
|
import_array1(0);
|
|
npy_intp dims[3] = {image->getWidth(), image->getHeight(), 4};
|
|
auto py = PyArray_SimpleNew(3, dims, NPY_UINT8);
|
|
memcpy(PyArray_DATA((PyArrayObject *)py), image->bits(), dims[0] * dims[1] * 4);
|
|
|
|
PYOBJECT_CALLFUNCTION(raw_image_callback, "O", py);
|
|
Py_DECREF(py);
|
|
|
|
done = true;
|
|
#else
|
|
PRINTFB(G, FB_Scene, FB_Errors)
|
|
" raw_image_callback-Error: no numpy support\n" ENDFB(G);
|
|
#endif
|
|
}
|
|
|
|
Py_XDECREF(raw_image_callback);
|
|
PAutoUnblock(G, blocked);
|
|
#endif
|
|
|
|
return done;
|
|
}
|
|
|
|
/**
|
|
* Creates an image of the current scene.
|
|
*
|
|
* @param extent requested extent
|
|
* @param antialias antialias mode
|
|
* @param dpi dots per inch
|
|
* @param format ??
|
|
* @param quiet if true, don't print messages
|
|
* @param out_img if not nullptr, store the image data here
|
|
* @param filename if not empty, store the image to this file as PNG
|
|
*/
|
|
|
|
static void SceneImage(PyMOLGlobals* G, const Extent2D& extent, int antialias,
|
|
float dpi, int format, bool quiet, pymol::Image* out_img,
|
|
const std::string& filename)
|
|
{
|
|
auto allButGizmos_i = pymol::to_underlying(SceneRenderWhich::All) &
|
|
~pymol::to_underlying(SceneRenderWhich::Gizmos);
|
|
auto allButGizmos = static_cast<SceneRenderWhich>(allButGizmos_i);
|
|
SceneMakeSizedImage(G, extent, antialias, /*excludeSelecions*/ true, allButGizmos);
|
|
if (!filename.empty()) {
|
|
ScenePNG(G, filename.c_str(), dpi, quiet, false, format, nullptr);
|
|
} else if (out_img) {
|
|
png_outbuf_t outbuf;
|
|
ScenePNG(G, "", dpi, quiet, false, format, &outbuf);
|
|
out_img->setVecData(std::move(outbuf));
|
|
} else if(call_raw_image_callback(G)) {
|
|
} else if(G->HaveGUI && SettingGetGlobal_b(G, cSetting_auto_copy_images)) {
|
|
#ifdef _PYMOL_IP_EXTRAS
|
|
if(IncentiveCopyToClipboard(G, di->quiet)) {
|
|
}
|
|
#else
|
|
#ifdef PYMOL_EVAL
|
|
PRINTFB(G, FB_Scene, FB_Warnings)
|
|
" Warning: Clipboard image transfers disabled in Evaluation builds.\n" ENDFB(G);
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool SceneDeferImage(PyMOLGlobals* G, const Extent2D& extent,
|
|
const char* filename, int antialias, float dpi, int format, int quiet,
|
|
pymol::Image* out_img)
|
|
{
|
|
std::string filenameStr = filename ? filename : "";
|
|
std::function<void()> deferred = [=]() {
|
|
SceneImage(G, extent, antialias, dpi, format, quiet, out_img, filenameStr);
|
|
};
|
|
|
|
if (G->ValidContext) {
|
|
deferred();
|
|
return false;
|
|
}
|
|
|
|
OrthoDefer(G, std::move(deferred));
|
|
return true;
|
|
}
|
|
|
|
int CScene::click(int button, int x, int y, int mod) // Originally SceneDeferClick!!
|
|
{
|
|
return SceneDeferClickWhen(this, button, x, y, UtilGetSeconds(m_G), mod);
|
|
}
|
|
|
|
static int SceneDeferClickWhen(Block * block, int button, int x, int y, double when,
|
|
int mod)
|
|
{
|
|
PyMOLGlobals *G = block->m_G;
|
|
std::function<void()> deferred = [=]() {
|
|
SceneClick(block, button, x, y, mod, when);
|
|
};
|
|
OrthoDefer(G, std::move(deferred));
|
|
return 1;
|
|
}
|
|
|
|
int CScene::drag(int x, int y, int mod) //Originally SceneDeferDrag
|
|
{
|
|
PyMOLGlobals *G = m_G;
|
|
auto when = UtilGetSeconds(G);
|
|
std::function<void()> deferred = [=]() {
|
|
SceneDrag(this, x, y, mod, when);
|
|
};
|
|
OrthoDefer(G, std::move(deferred));
|
|
return 1;
|
|
}
|
|
|
|
//static int SceneDeferredRelease(DeferredMouse * dm)
|
|
//{
|
|
// SceneRelease(dm->block, dm->button, dm->x, dm->y, dm->mod, dm->when);
|
|
// return 1;
|
|
//}
|
|
|
|
int CScene::release(int button, int x, int y, int mod) // Originally SceneDeferRelease
|
|
{
|
|
PyMOLGlobals *G = m_G;
|
|
auto when = UtilGetSeconds(G);
|
|
std::function<void()> deferred = [=]() {
|
|
SceneRelease(this, button, x, y, mod, when);
|
|
};
|
|
|
|
OrthoDefer(G, std::move(deferred));
|
|
return 1;
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneFree(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
#if !defined(PURE_OPENGL_ES_2) || defined(_WEBGL)
|
|
CGOFree(I->offscreenCGO);
|
|
#endif
|
|
|
|
CGOFree(I->AlphaCGO);
|
|
CGOFree(I->offscreenCGO);
|
|
CGOFree(I->offscreenOIT_CGO);
|
|
CGOFree(I->offscreenOIT_CGO_copy);
|
|
I->m_slots.clear();
|
|
I->Obj.clear();
|
|
I->GadgetObjs.clear();
|
|
I->NonGadgetObjs.clear();
|
|
|
|
ScenePurgeImage(G);
|
|
CGOFree(G->DebugCGO);
|
|
delete G->Scene;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneResetMatrix(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->m_view.setRotMatrix(glm::mat4(1.0f));
|
|
SceneUpdateInvMatrix(G);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneSetDefaultView(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
I->m_view.setRotMatrix(glm::mat4(1.0f));
|
|
SceneUpdateInvMatrix(G);
|
|
|
|
I->ViewNormal[0] = 0.0F;
|
|
I->ViewNormal[1] = 0.0F;
|
|
I->ViewNormal[2] = 1.0F;
|
|
|
|
I->m_view.setPos(0.0f, 0.0f, -50.0f);
|
|
I->m_view.setOrigin(0.0f, 0.0f, 0.0f);
|
|
|
|
I->m_view.m_clip().m_front = 40.0F;
|
|
I->m_view.m_clip().m_back = 100.0F;
|
|
UpdateFrontBackSafe(I);
|
|
|
|
I->Scale = 1.0F;
|
|
|
|
}
|
|
|
|
int SceneReinitialize(PyMOLGlobals * G)
|
|
{
|
|
int ok = true;
|
|
SceneSetDefaultView(G);
|
|
SceneCountFrames(G);
|
|
SceneSetFrame(G, 0, 0);
|
|
SceneInvalidate(G);
|
|
G->Scene->SceneVec.clear();
|
|
return (ok);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneInit(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = nullptr;
|
|
I = (G->Scene = new CScene(G));
|
|
if(I) {
|
|
assert(!I->RovingDirtyFlag);
|
|
assert(I->DirtyFlag);
|
|
|
|
/* all defaults to zero, so only initialize non-zero elements */
|
|
G->DebugCGO = CGONew(G);
|
|
|
|
I->LastClickTime = UtilGetSeconds(G);
|
|
|
|
SceneSetDefaultView(G);
|
|
|
|
I->active = true;
|
|
|
|
OrthoAttach(G, I, cOrthoScene);
|
|
|
|
I->LastRender = UtilGetSeconds(G);
|
|
I->LastFrameTime = UtilGetSeconds(G);
|
|
|
|
I->LastSweepTime = UtilGetSeconds(G);
|
|
|
|
SceneRestartFrameTimer(G);
|
|
SceneRestartPerfTimer(G);
|
|
|
|
/* scene list */
|
|
|
|
I->Pressed = -1;
|
|
I->Over = -1;
|
|
|
|
return 1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void CScene::reshape(int width, int height)
|
|
{
|
|
PyMOLGlobals *G = m_G;
|
|
CScene *I = G->Scene;
|
|
|
|
if(I->margin.right) {
|
|
width -= I->margin.right;
|
|
if(width < 1)
|
|
width = 1;
|
|
}
|
|
|
|
if(I->margin.top) {
|
|
height -= I->margin.top;
|
|
}
|
|
|
|
I->Width = width;
|
|
|
|
I->Height = height;
|
|
|
|
I->rect.top = I->Height;
|
|
I->rect.left = 0;
|
|
I->rect.bottom = 0;
|
|
I->rect.right = I->Width;
|
|
|
|
if(I->margin.bottom) {
|
|
height -= I->margin.bottom;
|
|
if(height < 1)
|
|
height = 1;
|
|
I->Height = height;
|
|
I->rect.bottom = I->rect.top - I->Height;
|
|
}
|
|
SceneDirty(G);
|
|
|
|
if(I->CopyType && (!I->CopyForced)) {
|
|
SceneInvalidateCopy(G, false);
|
|
}
|
|
/*MovieClearImages(G); */
|
|
MovieSetSize(G, I->Width, I->Height);
|
|
SceneInvalidateStencil(G);
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneResetNormal(PyMOLGlobals * G, int lines)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
if(lines)
|
|
glNormal3fv(I->LinesNormal);
|
|
else
|
|
glNormal3fv(I->ViewNormal);
|
|
}
|
|
}
|
|
|
|
void SceneResetNormalCGO(PyMOLGlobals * G, CGO *cgo, int lines)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
if(lines)
|
|
CGONormalv(cgo, I->LinesNormal);
|
|
else
|
|
CGONormalv(cgo, I->ViewNormal);
|
|
}
|
|
}
|
|
|
|
void SceneResetNormalToViewVector(PyMOLGlobals * G, short use_shader)
|
|
{
|
|
auto modMatrix = SceneGetModelViewMatrixPtr(G);
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
#if defined(PURE_OPENGL_ES_2)
|
|
glVertexAttrib3f(VERTEX_NORMAL, modMatrix[2], modMatrix[6], modMatrix[10]);
|
|
#else
|
|
if (use_shader){
|
|
glVertexAttrib3f(VERTEX_NORMAL, modMatrix[2], modMatrix[6], modMatrix[10]);
|
|
} else {
|
|
glNormal3f(modMatrix[2], modMatrix[6], modMatrix[10]);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void SceneResetNormalUseShader(PyMOLGlobals * G, int lines, short use_shader)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
#ifdef PURE_OPENGL_ES_2
|
|
if(lines)
|
|
glVertexAttrib3fv(VERTEX_NORMAL, I->LinesNormal);
|
|
else
|
|
glVertexAttrib3fv(VERTEX_NORMAL, I->ViewNormal);
|
|
#else
|
|
if (use_shader){
|
|
if(lines)
|
|
glVertexAttrib3fv(VERTEX_NORMAL, I->LinesNormal);
|
|
else
|
|
glVertexAttrib3fv(VERTEX_NORMAL, I->ViewNormal);
|
|
} else {
|
|
if(lines)
|
|
glNormal3fv(I->LinesNormal);
|
|
else
|
|
glNormal3fv(I->ViewNormal);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void SceneResetNormalUseShaderAttribute(PyMOLGlobals * G, int lines, short use_shader, int attr)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
#ifdef PURE_OPENGL_ES_2
|
|
if (attr < 0)
|
|
return;
|
|
if(lines)
|
|
glVertexAttrib3fv(attr, I->LinesNormal);
|
|
else
|
|
glVertexAttrib3fv(attr, I->ViewNormal);
|
|
#else
|
|
if (use_shader){
|
|
if(lines)
|
|
glVertexAttrib3fv(attr, I->LinesNormal);
|
|
else
|
|
glVertexAttrib3fv(attr, I->ViewNormal);
|
|
} else {
|
|
if(lines)
|
|
glNormal3fv(I->LinesNormal);
|
|
else
|
|
glNormal3fv(I->ViewNormal);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void SceneGetResetNormal(PyMOLGlobals * G, float *normal, int lines)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float *norm;
|
|
if(G->HaveGUI && G->ValidContext) {
|
|
if(lines)
|
|
norm = I->LinesNormal;
|
|
else
|
|
norm = I->ViewNormal;
|
|
normal[0] = norm[0]; normal[1] = norm[1]; normal[2] = norm[2];
|
|
}
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneApplyImageGamma(PyMOLGlobals * G, unsigned int *buffer, int width,
|
|
int height)
|
|
{
|
|
float gamma = SettingGetGlobal_f(G, cSetting_gamma);
|
|
|
|
if(gamma > R_SMALL4)
|
|
gamma = 1.0F / gamma;
|
|
else
|
|
gamma = 1.0F;
|
|
|
|
if(buffer && height && width) {
|
|
float _inv3 = 1 / (255 * 3.0F);
|
|
float _1 = 1 / 3.0F;
|
|
unsigned char *p;
|
|
int x, y;
|
|
float c1, c2, c3, inp, sig;
|
|
unsigned int i1, i2, i3;
|
|
p = (unsigned char *) buffer;
|
|
for(y = 0; y < height; y++) {
|
|
for(x = 0; x < width; x++) {
|
|
c1 = p[0];
|
|
c2 = p[1];
|
|
c3 = p[2];
|
|
inp = (c1 + c2 + c3) * _inv3;
|
|
if(inp < R_SMALL4)
|
|
sig = _1;
|
|
else
|
|
sig = (float) (pow(inp, gamma) / inp);
|
|
i1 = (unsigned int) (sig * c1);
|
|
i2 = (unsigned int) (sig * c2);
|
|
i3 = (unsigned int) (sig * c3);
|
|
if(i1 > 255)
|
|
i1 = 255;
|
|
if(i2 > 255)
|
|
i2 = 255;
|
|
if(i3 > 255)
|
|
i3 = 255;
|
|
p[0] = i1;
|
|
p[1] = i2;
|
|
p[2] = i3;
|
|
p += 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SceneUpdateAnimation(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
int rockFlag = false;
|
|
int dirty = false;
|
|
int movie_rock = SettingGetGlobal_i(G, cSetting_movie_rock);
|
|
|
|
if(movie_rock < 0)
|
|
movie_rock = ControlRocking(G);
|
|
|
|
if(MoviePlaying(G) && movie_rock) {
|
|
|
|
if(MovieGetRealtime(G) && !SettingGetGlobal_b(G, cSetting_movie_animate_by_frame)) {
|
|
I->RenderTime = UtilGetSeconds(G) - I->LastSweepTime;
|
|
rockFlag = true;
|
|
dirty = true; /* force a subsequent update */
|
|
} else {
|
|
float fps = SceneGetFPS(G); /* guaranteed to be >= 0.0F */
|
|
if(fps > 0.0F) {
|
|
int rock_frame = SceneGetFrame(G);
|
|
if(rock_frame != I->RockFrame) {
|
|
I->RockFrame = rock_frame;
|
|
rockFlag = true;
|
|
I->RenderTime = 1.0 / fps;
|
|
}
|
|
} else {
|
|
I->RenderTime = UtilGetSeconds(G) - I->LastSweepTime;
|
|
rockFlag = true;
|
|
}
|
|
}
|
|
} else
|
|
dirty = true;
|
|
|
|
if(I->cur_ani_elem < I->n_ani_elem) { /* play motion animation */
|
|
double now;
|
|
|
|
int cur = I->cur_ani_elem;
|
|
|
|
if(I->AnimationStartFlag) {
|
|
/* allow animation timing to lag since it may take a few seconds
|
|
to get here given geometry updates, etc. */
|
|
|
|
I->AnimationLagTime = UtilGetSeconds(G) - I->AnimationStartTime;
|
|
I->AnimationStartFlag = false;
|
|
}
|
|
|
|
if((!MoviePlaying(G)) ||
|
|
((MovieGetRealtime(G) &&
|
|
!SettingGetGlobal_b(G, cSetting_movie_animate_by_frame)))) {
|
|
now = UtilGetSeconds(G) - I->AnimationLagTime;
|
|
} else {
|
|
float fps = SceneGetFPS(G); /* guaranteed to be >= 0.0F */
|
|
int frame = SceneGetFrame(G);
|
|
int n_frame = 0;
|
|
|
|
cur = 0; /* allow backwards interpolation */
|
|
if(frame >= I->AnimationStartFrame) {
|
|
n_frame = frame - I->AnimationStartFrame;
|
|
} else {
|
|
n_frame = frame + (I->NFrame - I->AnimationStartFrame);
|
|
}
|
|
now = I->AnimationStartTime + n_frame / fps;
|
|
}
|
|
|
|
while(I->ani_elem[cur].timing < now) {
|
|
cur++;
|
|
if(cur >= I->n_ani_elem) {
|
|
cur = I->n_ani_elem;
|
|
break;
|
|
}
|
|
}
|
|
I->cur_ani_elem = cur;
|
|
SceneFromViewElem(G, I->ani_elem + cur, dirty);
|
|
OrthoDirty(G);
|
|
}
|
|
if(rockFlag && (I->RenderTime != 0.0)) {
|
|
SceneUpdateCameraRock(G, dirty);
|
|
}
|
|
}
|
|
|
|
int SceneGetDrawFlag(GridInfo * grid, int *slot_vla, int slot)
|
|
{
|
|
int draw_flag = false;
|
|
if(grid && grid->active) {
|
|
switch (grid->mode) {
|
|
case GridMode::ByObject: /* assigned grid slots (usually by group) */
|
|
{
|
|
if(((slot < 0) && grid->slot) ||
|
|
((slot == 0) && (grid->slot == 0)) ||
|
|
(slot_vla && (slot >= 0 && slot_vla[slot] == grid->slot))) {
|
|
draw_flag = true;
|
|
}
|
|
}
|
|
break;
|
|
case GridMode::ByObjectStates:
|
|
case GridMode::ByObjectByState:
|
|
draw_flag = true;
|
|
break;
|
|
}
|
|
} else {
|
|
draw_flag = true;
|
|
}
|
|
return draw_flag;
|
|
}
|
|
|
|
int SceneGetDrawFlagGrid(PyMOLGlobals * G, GridInfo * grid, int slot)
|
|
{
|
|
CScene *I = G->Scene;
|
|
return SceneGetDrawFlag(grid, I->m_slots.data(), slot);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneCopy(PyMOLGlobals * G, GLFramebufferConfig config, int force, int entire_window)
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
if(force || (!(I->StereoMode ||
|
|
SettingGetGlobal_b(G, cSetting_stereo_double_pump_mono) || I->ButtonsShown))) {
|
|
/* no copies while in stereo mode */
|
|
if(force || ((!I->DirtyFlag) && (!I->CopyType))) {
|
|
Rect2D rect;
|
|
if(entire_window) {
|
|
rect = OrthoGetRect(G);
|
|
} else {
|
|
rect = Rect2D{{}, SceneGetExtent(G)};
|
|
}
|
|
ScenePurgeImage(G);
|
|
auto imgData = G->ShaderMgr->readPixelsFrom(G, rect, config);
|
|
if (!imgData.empty()) {
|
|
I->Image = std::make_shared<pymol::Image>(rect.extent.width, rect.extent.height);
|
|
I->Image->setVecData(std::move(imgData));
|
|
}
|
|
I->CopyType = true;
|
|
I->Image->m_needs_alpha_reset = true;
|
|
I->CopyForced = force;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*========================================================================*/
|
|
int SceneRovingCheckDirty(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
return (I->RovingDirtyFlag);
|
|
}
|
|
|
|
struct CObjectUpdateThreadInfo {
|
|
pymol::CObject *obj;
|
|
};
|
|
|
|
void SceneObjectUpdateThread(CObjectUpdateThreadInfo * T)
|
|
{
|
|
if(T->obj) {
|
|
T->obj->update();
|
|
}
|
|
}
|
|
|
|
#ifndef _PYMOL_NOPY
|
|
static void SceneObjectUpdateSpawn(PyMOLGlobals * G, CObjectUpdateThreadInfo * Thread,
|
|
int n_thread, int n_total)
|
|
{
|
|
if(n_total == 1) {
|
|
SceneObjectUpdateThread(Thread);
|
|
} else if(n_total) {
|
|
int blocked;
|
|
PyObject *info_list;
|
|
int a, n = 0;
|
|
blocked = PAutoBlock(G);
|
|
|
|
PRINTFB(G, FB_Scene, FB_Blather)
|
|
" Scene: updating objects with %d threads...\n", n_thread ENDFB(G);
|
|
info_list = PyList_New(n_total);
|
|
for(a = 0; a < n_total; a++) {
|
|
PyList_SetItem(info_list, a, PyCapsule_New(Thread + a, nullptr, nullptr));
|
|
n++;
|
|
}
|
|
PXDecRef(PYOBJECT_CALLMETHOD
|
|
(G->P_inst->cmd, "_object_update_spawn", "Oi", info_list, n_thread));
|
|
Py_DECREF(info_list);
|
|
PAutoUnblock(G, blocked);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void SceneStencilCheck(PyMOLGlobals *G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
if( I->StereoMode == cStereo_stencil_by_row ) {
|
|
int bottom = 0;
|
|
|
|
#ifndef _PYMOL_PRETEND_GLUT
|
|
if(G->Main)
|
|
bottom = p_glutGet(P_GLUT_WINDOW_Y) + p_glutGet(P_GLUT_WINDOW_HEIGHT);
|
|
#endif
|
|
|
|
int parity = bottom & 0x1;
|
|
if(parity != I->StencilParity) {
|
|
I->StencilValid = false;
|
|
I->StencilParity = parity;
|
|
SceneDirty(G);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneUpdate(PyMOLGlobals * G, int force)
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
int cur_state = SettingGetGlobal_i(G, cSetting_state) - 1;
|
|
int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);
|
|
|
|
PRINTFD(G, FB_Scene)
|
|
" SceneUpdate: entered.\n" ENDFD;
|
|
|
|
OrthoBusyPrime(G);
|
|
WizardDoPosition(G, false);
|
|
WizardDoView(G, false);
|
|
EditorUpdate(G);
|
|
SceneStencilCheck(G);
|
|
|
|
if(defer_builds_mode == 0) {
|
|
if(SettingGetGlobal_i(G, cSetting_draw_mode) == -2) {
|
|
defer_builds_mode = 1;
|
|
}
|
|
}
|
|
|
|
if(force || I->ChangedFlag || ((cur_state != I->LastStateBuilt) &&
|
|
(defer_builds_mode > 0))) {
|
|
|
|
SceneCountFrames(G);
|
|
|
|
if(force || (defer_builds_mode != 5)) { /* mode 5 == immediate mode */
|
|
|
|
PyMOL_SetBusy(G->PyMOL, true); /* race condition -- may need to be fixed */
|
|
|
|
/* update all gadgets first (single-threaded since they're thread-unsafe) */
|
|
for (auto& GadgetObj : I->GadgetObjs) {
|
|
GadgetObj->update();
|
|
}
|
|
|
|
{
|
|
#ifndef _PYMOL_NOPY
|
|
int n_thread = SettingGetGlobal_i(G, cSetting_max_threads);
|
|
int multithread = SettingGetGlobal_i(G, cSetting_async_builds);
|
|
if(multithread && (n_thread > 1)) {
|
|
int min_start = -1;
|
|
int max_stop = -1;
|
|
int n_obj = 0;
|
|
for (auto& obj : I->Obj) {
|
|
int start = 0;
|
|
n_obj++;
|
|
int stop = obj->getNFrame();
|
|
/* set start/stop to define the range for this object
|
|
* depending upon various build settings */
|
|
ObjectAdjustStateRebuildRange(obj, &start, &stop);
|
|
if(min_start < 0) {
|
|
min_start = start;
|
|
max_stop = stop;
|
|
} else {
|
|
if(min_start > start)
|
|
min_start = start;
|
|
if(max_stop < stop)
|
|
max_stop = stop;
|
|
}
|
|
}
|
|
|
|
int n_frame = max_stop - min_start;
|
|
|
|
if(n_frame > n_thread) {
|
|
n_thread = 1;
|
|
/* prevent n_thread * n_thread -- only multithread within
|
|
individual object states (typically more balanced) */
|
|
} else if(n_frame > 1) {
|
|
n_thread = n_thread / n_frame;
|
|
}
|
|
|
|
if(n_thread < 1)
|
|
n_thread = 1;
|
|
}
|
|
|
|
/* Note: we might want to optimize this by doing multi-threaded updates
|
|
for all objects. */
|
|
if(multithread && (n_thread > 1)) {
|
|
/* multi-threaded geometry update */
|
|
int cnt = I->NonGadgetObjs.size();
|
|
|
|
if(cnt) {
|
|
CObjectUpdateThreadInfo *thread_info = pymol::malloc<CObjectUpdateThreadInfo>(cnt);
|
|
if(thread_info) {
|
|
cnt = 0;
|
|
for (auto& NonGadgetObj : I->NonGadgetObjs) {
|
|
thread_info[cnt++].obj = NonGadgetObj;
|
|
}
|
|
SceneObjectUpdateSpawn(G, thread_info, n_thread, cnt);
|
|
FreeP(thread_info);
|
|
}
|
|
}
|
|
} else
|
|
#endif
|
|
/* single-threaded update */
|
|
for (auto& obj : I->Obj) {
|
|
obj->update();
|
|
}
|
|
}
|
|
PyMOL_SetBusy(G->PyMOL, false); /* race condition -- may need to be fixed */
|
|
} else { /* defer builds mode == 5 -- for now, only update non-molecular objects */
|
|
/* single-threaded update */
|
|
for (auto& obj : I->Obj) {
|
|
if(obj->type != cObjectMolecule) {
|
|
obj->update();
|
|
}
|
|
}
|
|
}
|
|
|
|
I->ChangedFlag = false;
|
|
|
|
if((defer_builds_mode >= 2) && (force || (defer_builds_mode != 5)) &&
|
|
(cur_state != I->LastStateBuilt)) {
|
|
/* purge graphics representation when no longer used */
|
|
if(I->LastStateBuilt >= 0) {
|
|
for ( auto it = I->Obj.begin(); it != I->Obj.end(); ++it) {
|
|
if ((*it)->type != cObjectMolecule || force || defer_builds_mode != 5) {
|
|
int static_singletons =
|
|
SettingGet_b(G, (*it)->Setting.get(), nullptr, cSetting_static_singletons);
|
|
int async_builds =
|
|
SettingGet_b(G, (*it)->Setting.get(), nullptr, cSetting_async_builds);
|
|
int max_threads =
|
|
SettingGet_i(G, (*it)->Setting.get(), nullptr, cSetting_max_threads);
|
|
int nFrame = 0;
|
|
nFrame = (*it)->getNFrame();
|
|
if((nFrame > 1) || (!static_singletons)) {
|
|
int start = I->LastStateBuilt;
|
|
int stop = start + 1;
|
|
int ste;
|
|
if(async_builds && (max_threads > 1)) {
|
|
if((start / max_threads) == (cur_state / max_threads)) {
|
|
stop = start; /* don't purge current batch */
|
|
} else {
|
|
int base = start / max_threads; /* now purge previous batch */
|
|
start = base * max_threads;
|
|
stop = (base + 1) * max_threads;
|
|
}
|
|
}
|
|
for(ste = start; ste < stop; ste++) {
|
|
(*it)->invalidate(cRepAll, cRepInvPurge, ste);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
I->LastStateBuilt = cur_state;
|
|
WizardDoScene(G);
|
|
if(!MovieDefined(G)) {
|
|
if(SettingGetGlobal_i(G, cSetting_frame) != (cur_state + 1))
|
|
SettingSetGlobal_i(G, cSetting_frame, (cur_state + 1));
|
|
}
|
|
}
|
|
|
|
PRINTFD(G, FB_Scene)
|
|
" %s: leaving...\n", __func__ ENDFD;
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
int SceneRenderCached(PyMOLGlobals * G)
|
|
{
|
|
/* sets up a cached image buffer is one is available, or if we are
|
|
* using cached images by default */
|
|
CScene *I = G->Scene;
|
|
std::shared_ptr<pymol::Image> image;
|
|
int renderedFlag = false;
|
|
int draw_mode = SettingGetGlobal_i(G, cSetting_draw_mode);
|
|
PRINTFD(G, FB_Scene)
|
|
" %s: entered.\n", __func__ ENDFD;
|
|
|
|
G->ShaderMgr->Check_Reload();
|
|
if(I->DirtyFlag) {
|
|
int moviePlaying = MoviePlaying(G);
|
|
|
|
if(I->MovieFrameFlag || (moviePlaying && SettingGetGlobal_b(G, cSetting_cache_frames))) {
|
|
I->MovieFrameFlag = false;
|
|
image = MovieGetImage(G,
|
|
MovieFrameToImage(G,
|
|
SettingGetGlobal_i(G, cSetting_frame) - 1));
|
|
if(image) {
|
|
if(I->Image){
|
|
ScenePurgeImage(G);
|
|
}
|
|
I->CopyType = true;
|
|
I->Image = image;
|
|
OrthoDirty(G);
|
|
renderedFlag = true;
|
|
} else {
|
|
SceneMakeMovieImage(G, true, false, cSceneImage_Default);
|
|
renderedFlag = true;
|
|
}
|
|
} else if(draw_mode == 3) {
|
|
auto show_progress = SettingGet<int>(G, cSetting_show_progress);
|
|
SettingSetGlobal_i(G, cSetting_show_progress, 0);
|
|
SceneRay(G, 0, 0, SettingGetGlobal_i(G, cSetting_ray_default_renderer),
|
|
nullptr, nullptr, 0.0F, 0.0F, false, nullptr, false, -1);
|
|
SettingSetGlobal_i(G,cSetting_show_progress, show_progress);
|
|
} else if(moviePlaying && SettingGetGlobal_b(G, cSetting_ray_trace_frames)) {
|
|
SceneRay(G, 0, 0, SettingGetGlobal_i(G, cSetting_ray_default_renderer),
|
|
nullptr, nullptr, 0.0F, 0.0F, false, nullptr, true, -1);
|
|
} else if((moviePlaying && SettingGetGlobal_b(G, cSetting_draw_frames)) || (draw_mode == 2)) {
|
|
Extent2D extent {0u, 0u};
|
|
SceneMakeSizedImage(G, extent, SettingGetGlobal_i(G, cSetting_antialias), /*excludeSelections*/ false);
|
|
} else if(I->CopyType == true) { /* true vs. 2 */
|
|
renderedFlag = true;
|
|
} else {
|
|
renderedFlag = false;
|
|
}
|
|
} else if(I->CopyType == true) { /* true vs. 2 */
|
|
renderedFlag = true;
|
|
}
|
|
|
|
PRINTFD(G, FB_Scene)
|
|
" %s: leaving...renderedFlag %d\n", __func__, renderedFlag ENDFD;
|
|
|
|
return (renderedFlag);
|
|
}
|
|
|
|
float SceneGetSpecularValue(PyMOLGlobals * G, float spec, int limit)
|
|
{
|
|
int n_light = SettingGetGlobal_i(G, cSetting_spec_count);
|
|
if(n_light < 0)
|
|
n_light = SettingGetGlobal_i(G, cSetting_light_count);
|
|
if(n_light > limit)
|
|
n_light = limit;
|
|
if(n_light > 2) {
|
|
spec = spec / pow(n_light - 1, 0.6F);
|
|
}
|
|
return (spec > 1.F) ? 1.F : (spec < 0.F) ? 0.F : spec;
|
|
}
|
|
|
|
float SceneGetReflectScaleValue(PyMOLGlobals * G, int limit)
|
|
{
|
|
float result = 1.0F;
|
|
int n_light = SettingGetGlobal_i(G, cSetting_light_count);
|
|
if(n_light > limit)
|
|
n_light = limit;
|
|
if(n_light > 1) {
|
|
float tmp[3];
|
|
float sum = 0.0F;
|
|
for (int i = 0; i < n_light - 1; ++i) {
|
|
copy3f(SettingGetGlobal_3fv(G, light_setting_indices[i]), tmp);
|
|
normalize3f(tmp);
|
|
sum += 1.f - tmp[2];
|
|
}
|
|
sum *= 0.5;
|
|
return result / sum;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SceneGetModel2WorldMatrix(PyMOLGlobals * G, float *matrix) {
|
|
CScene *I = G->Scene;
|
|
if (!I)
|
|
return;
|
|
|
|
identity44f(matrix);
|
|
const auto& pos = I->m_view.pos();
|
|
const auto& ori = I->m_view.origin();
|
|
MatrixTranslateC44f(matrix, pos.x, pos.y, pos.z);
|
|
MatrixMultiplyC44f(glm::value_ptr(I->m_view.rotMatrix()), matrix);
|
|
MatrixTranslateC44f(matrix, -ori.x, -ori.y, -ori.z);
|
|
}
|
|
|
|
void SceneSetModel2WorldMatrix(PyMOLGlobals * G, float const *matrix) {
|
|
CScene *I = G->Scene;
|
|
if (!I)
|
|
return;
|
|
|
|
// build inverse origin translate
|
|
float invOriginTranslate[16];
|
|
identity44f(invOriginTranslate);
|
|
const auto& ori = I->m_view.origin();
|
|
MatrixTranslateC44f(invOriginTranslate, ori.x, ori.y, ori.z);
|
|
// get shiftRot from m2wNew
|
|
float temp[16];
|
|
memcpy(temp, matrix, sizeof(temp));
|
|
MatrixMultiplyC44f(invOriginTranslate, temp);
|
|
I->m_view.setPos(temp[12], temp[13], temp[14]);
|
|
// decompose shiftRot
|
|
temp[12] = temp[13] = temp[14] = 0.0f;
|
|
I->m_view.setRotMatrix(glm::make_mat4(temp));
|
|
}
|
|
|
|
/**
|
|
* Get specular and shininess, adjusted to the number of lights.
|
|
*
|
|
* @param[out] ptr_spec specular for lights 2-N
|
|
* @param[out] ptr_spec_power shininess for lights 2-N
|
|
* @param[out] ptr_spec_direct specular for light 1
|
|
* @param[out] ptr_spec_direct_power shininess for light 1
|
|
* @param limit number of lights (e.g. `light_count` setting)
|
|
*/
|
|
void SceneGetAdjustedLightValues(PyMOLGlobals * G,
|
|
float *ptr_spec,
|
|
float *ptr_spec_power,
|
|
float *ptr_spec_direct,
|
|
float *ptr_spec_direct_power,
|
|
int limit)
|
|
{
|
|
float specular = SettingGetGlobal_f(G, cSetting_specular);
|
|
if (specular == 1.0F)
|
|
specular = SettingGetGlobal_f(G, cSetting_specular_intensity);
|
|
if (specular < R_SMALL4)
|
|
specular = 0.0F;
|
|
|
|
float spec_power = SettingGetGlobal_f(G, cSetting_spec_power);
|
|
if (spec_power < 0.0F)
|
|
spec_power = SettingGetGlobal_f(G, cSetting_shininess);
|
|
|
|
float spec_reflect = SettingGetGlobal_f(G, cSetting_spec_reflect);
|
|
if (spec_reflect < 0.0F)
|
|
spec_reflect = specular;
|
|
|
|
float spec_direct = SettingGetGlobal_f(G, cSetting_spec_direct);
|
|
if (spec_direct < 0.0F)
|
|
spec_direct = specular;
|
|
|
|
float spec_direct_power = SettingGetGlobal_f(G, cSetting_spec_direct_power);
|
|
if (spec_direct_power < 0.0F)
|
|
spec_direct_power = spec_power;
|
|
|
|
*ptr_spec = SceneGetSpecularValue(G, spec_reflect, limit);
|
|
*ptr_spec_power = spec_power;
|
|
*ptr_spec_direct = spec_direct > 1.F ? 1.F : spec_direct;
|
|
*ptr_spec_direct_power = spec_direct_power;
|
|
}
|
|
|
|
/*
|
|
* Shader attribute names
|
|
*/
|
|
#define TEMPLATE(i) "g_LightSource[" #i "].position"
|
|
const char * lightsource_position_names[] = {
|
|
TEMPLATE(0), TEMPLATE(1), TEMPLATE(2), TEMPLATE(3), TEMPLATE(4),
|
|
TEMPLATE(5), TEMPLATE(6), TEMPLATE(7), TEMPLATE(8), TEMPLATE(9)
|
|
};
|
|
#undef TEMPLATE
|
|
|
|
#define TEMPLATE(i) "g_LightSource[" #i "].diffuse"
|
|
const char * lightsource_diffuse_names[] = {
|
|
TEMPLATE(0), TEMPLATE(1), TEMPLATE(2), TEMPLATE(3), TEMPLATE(4),
|
|
TEMPLATE(5), TEMPLATE(6), TEMPLATE(7), TEMPLATE(8), TEMPLATE(9)
|
|
};
|
|
#undef TEMPLATE
|
|
|
|
/**
|
|
* Sets up lighting for immediate mode if shaderPrg=nullptr, otherwise
|
|
* sets lighting uniforms for the given shader program.
|
|
*
|
|
* Supports up to light_count=8
|
|
*/
|
|
void SceneProgramLighting(PyMOLGlobals * G, CShaderPrg * shaderPrg)
|
|
{
|
|
|
|
/* load up the light positions relative to the camera while
|
|
MODELVIEW still has the identity */
|
|
int n_light = glm::clamp(SettingGetGlobal_i(G, cSetting_light_count), 0, 8);
|
|
int spec_count = SettingGetGlobal_i(G, cSetting_spec_count);
|
|
float direct = SettingGetGlobal_f(G, cSetting_direct);
|
|
float reflect = SettingGetGlobal_f(G, cSetting_reflect) * SceneGetReflectScaleValue(G, n_light);
|
|
float spec[4];
|
|
float diff[4];
|
|
const float zero[4] = { 0.0F, 0.0F, 0.0F, 1.0F };
|
|
float vv[4] = {0.F, 0.F, 1.F, 0.F}; // position
|
|
float spec_value, shine, spec_direct, spec_direct_power;
|
|
|
|
SceneGetAdjustedLightValues(G,
|
|
&spec_value,
|
|
&shine,
|
|
&spec_direct,
|
|
&spec_direct_power,
|
|
n_light);
|
|
|
|
if (n_light < 2) {
|
|
direct += reflect;
|
|
if(direct > 1.0F)
|
|
direct = 1.0F;
|
|
}
|
|
|
|
if(spec_count < 0) {
|
|
spec_count = n_light;
|
|
}
|
|
|
|
// light 0
|
|
|
|
white4f(diff, SettingGetGlobal_f(G, cSetting_ambient));
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if (!shaderPrg) {
|
|
glEnable(GL_LIGHTING);
|
|
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, diff);
|
|
glLightfv(GL_LIGHT0, GL_POSITION, vv);
|
|
glLightfv(GL_LIGHT0, GL_AMBIENT, zero);
|
|
if(direct > R_SMALL4) {
|
|
white4f(diff, direct);
|
|
white4f(spec, spec_direct);
|
|
glEnable(GL_LIGHT0);
|
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, diff);
|
|
glLightfv(GL_LIGHT0, GL_SPECULAR, spec);
|
|
} else {
|
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, zero);
|
|
glLightfv(GL_LIGHT0, GL_SPECULAR, zero);
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
shaderPrg->Set4fv("g_LightModel.ambient", diff);
|
|
white4f(diff, (direct > R_SMALL4) ? direct : 0.f);
|
|
shaderPrg->Set4fv(lightsource_diffuse_names[0], diff);
|
|
shaderPrg->Set4fv(lightsource_position_names[0], vv);
|
|
}
|
|
|
|
// light 1-N
|
|
|
|
white4f(spec, spec_value);
|
|
white4f(diff, reflect);
|
|
|
|
for (int i = 1; i < n_light; ++i) {
|
|
// normalized/inverted light direction
|
|
copy3f(SettingGetGlobal_3fv(G, light_setting_indices[i - 1]), vv);
|
|
normalize3f(vv);
|
|
invert3f(vv);
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if (!shaderPrg) {
|
|
glEnable(GL_LIGHT0 + i);
|
|
glLightfv(GL_LIGHT0 + i, GL_POSITION, vv);
|
|
glLightfv(GL_LIGHT0 + i, GL_SPECULAR, (spec_count >= i) ? spec : zero);
|
|
glLightfv(GL_LIGHT0 + i, GL_AMBIENT, zero);
|
|
glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, diff);
|
|
} else
|
|
#endif
|
|
{
|
|
shaderPrg->Set4fv(lightsource_position_names[i], vv);
|
|
shaderPrg->Set4fv(lightsource_diffuse_names[i], diff);
|
|
}
|
|
}
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if (!shaderPrg) {
|
|
// TODO: this was depending on two_sided_lighting, surface_cavity_mode
|
|
// and transparency_mode
|
|
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
|
|
|
|
// disable unused lights
|
|
for (int i = 7; i >= n_light; --i) {
|
|
glDisable(GL_LIGHT0 + i);
|
|
}
|
|
|
|
// material
|
|
white4f(spec, 1.F);
|
|
glMaterialfv(GL_FRONT, GL_SPECULAR, spec);
|
|
glMaterialf(GL_FRONT, GL_SHININESS, glm::clamp(shine, 0.F, 128.F));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Set up the Scene Fog* member variables and immediate mode fog (incl.
|
|
* gl_Fog struct for non-ES2 shaders)
|
|
*/
|
|
int SceneSetFog(PyMOLGlobals *G){
|
|
CScene *I = G->Scene;
|
|
int fog_active = false;
|
|
float fog_density = SettingGetGlobal_f(G, cSetting_fog);
|
|
I->FogStart = (I->m_view.m_clipSafe().m_back - I->m_view.m_clipSafe().m_front) * SettingGetGlobal_f(G, cSetting_fog_start) + I->m_view.m_clipSafe().m_front;
|
|
if((fog_density > R_SMALL8) && (fog_density != 1.0F)) {
|
|
I->FogEnd = I->FogStart + (I->m_view.m_clipSafe().m_back - I->FogStart) / fog_density;
|
|
} else {
|
|
I->FogEnd = I->m_view.m_clipSafe().m_back;
|
|
}
|
|
|
|
if(SettingGetGlobal_b(G, cSetting_depth_cue) && fog_density != 0.0F) {
|
|
fog_active = true;
|
|
}
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if (ALWAYS_IMMEDIATE_OR(!SettingGetGlobal_b(G, cSetting_use_shaders))) {
|
|
const float *bg_rgb = ColorGet(G, SettingGetGlobal_color(G, cSetting_bg_rgb));
|
|
float fog[4] = {bg_rgb[0], bg_rgb[1], bg_rgb[2], 1.0};
|
|
|
|
glFogf(GL_FOG_MODE, GL_LINEAR);
|
|
glFogf(GL_FOG_START, I->FogStart);
|
|
glFogf(GL_FOG_END, I->FogEnd);
|
|
glFogf(GL_FOG_DENSITY, fog_density > R_SMALL8 ? fog_density : 1.0F);
|
|
glFogfv(GL_FOG_COLOR, fog);
|
|
if (fog_active)
|
|
glEnable(GL_FOG);
|
|
else
|
|
glDisable(GL_FOG);
|
|
}
|
|
#endif
|
|
|
|
return fog_active;
|
|
}
|
|
|
|
/**
|
|
* Set the g_Fog_* uniforms for ES2 shaders
|
|
*/
|
|
void SceneSetFogUniforms(PyMOLGlobals * G, CShaderPrg * shaderPrg) {
|
|
CScene *I = G->Scene;
|
|
if (shaderPrg) {
|
|
float fogScale = 1.0f / (I->FogEnd - I->FogStart);
|
|
shaderPrg->Set1f("g_Fog_end", I->FogEnd);
|
|
shaderPrg->Set1f("g_Fog_scale", fogScale);
|
|
}
|
|
}
|
|
|
|
void SceneSetupGLPicking(PyMOLGlobals * G){
|
|
/* picking mode: we want flat, unshaded, unblended, unsmooth colors */
|
|
|
|
glDisable(GL_FOG);
|
|
glDisable(GL_COLOR_MATERIAL);
|
|
glDisable(GL_LIGHTING);
|
|
glDisable(GL_LINE_SMOOTH);
|
|
glDisable(GL_DITHER);
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_POLYGON_SMOOTH);
|
|
if(G->Option->multisample)
|
|
glDisable(0x809D); /* GL_MULTISAMPLE_ARB */
|
|
glShadeModel(GL_FLAT);
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneRestartFrameTimer(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->LastFrameTime = UtilGetSeconds(G);
|
|
}
|
|
|
|
static void SceneRestartPerfTimer(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->LastRender = UtilGetSeconds(G);
|
|
I->RenderTime = 0.0;
|
|
}
|
|
|
|
void SceneRestartSweepTimer(PyMOLGlobals * G)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->LastSweep = 0.0F; /* continue to defer rocking until this is done */
|
|
I->LastSweepX = 0.0F;
|
|
I->LastSweepY = 0.0F;
|
|
I->SweepTime = 0.0;
|
|
I->LastSweepTime = UtilGetSeconds(G);
|
|
SceneRestartPerfTimer(G);
|
|
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void ScenePrepareMatrix(PyMOLGlobals * G, int mode, int stereo_mode /* = 0 */)
|
|
{
|
|
CScene *I = G->Scene;
|
|
|
|
float stAng, stShift;
|
|
const auto& pos = I->m_view.pos();
|
|
const auto& ori = I->m_view.origin();
|
|
|
|
#ifdef _PYMOL_OPENVR
|
|
bool isOpenVR = (stereo_mode == cStereo_openvr) && OpenVRReady(G);
|
|
|
|
if(isOpenVR) {
|
|
|
|
/* stereo OpenVR */
|
|
|
|
if (!mode) {
|
|
// average projection matrix for picking
|
|
glMatrixMode(GL_PROJECTION);
|
|
OpenVRLoadPickingProjectionMatrix(G, I->m_view.m_clipSafe().m_front, I->m_view.m_clipSafe().m_back);
|
|
|
|
// mono matrix for picking
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadMatrixf(OpenVRGetPickingMatrix(G));
|
|
|
|
} else {
|
|
glMatrixMode(GL_PROJECTION);
|
|
OpenVRLoadProjectionMatrix(G, I->m_view.m_clipSafe().m_front, I->m_view.m_clipSafe().m_back);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
OpenVRLoadWorld2EyeMatrix(G);
|
|
|
|
}
|
|
|
|
if (OpenVRIsMoleculeCaptured(G)) {
|
|
float scaler;
|
|
float const *mol2world = OpenVRGetMolecule2WorldMatrix(G, &scaler);
|
|
// save old plane shifts
|
|
float dist = fabsf(pos.z);
|
|
float frontShift = fabsf(dist - I->m_view.m_clip().m_front);
|
|
float backShift = fabsf(dist - I->m_view.m_clip().m_back);
|
|
// apply new transform to molecule
|
|
SceneSetModel2WorldMatrix(G, mol2world);
|
|
SceneScale(G, scaler);
|
|
// renew front and back planes
|
|
dist = fabsf(pos.z);
|
|
SceneClipSet(G, dist - frontShift * scaler, dist + backShift * scaler);
|
|
}
|
|
|
|
/* move the camera to the location we are looking at */
|
|
glTranslatef(pos.x, pos.y, pos.z);
|
|
|
|
/* scale molecule */
|
|
glScalef(I->Scale, I->Scale, I->Scale);
|
|
|
|
/* rotate about the origin (the the center of rotation) */
|
|
glMultMatrixf(glm::value_ptr(I->m_view.rotMatrix()));
|
|
|
|
/* move the origin to the center of rotation */
|
|
glTranslatef(-ori.x, -ori.y, -ori.z);
|
|
|
|
// TODO don't do the immediate mode detour
|
|
glGetFloatv(GL_PROJECTION_MATRIX, SceneGetProjectionMatrixPtr(G));
|
|
glGetFloatv(GL_MODELVIEW_MATRIX, SceneGetModelViewMatrixPtr(G));
|
|
|
|
} else
|
|
#endif
|
|
{
|
|
if (!mode){
|
|
SceneComposeModelViewMatrix(I, SceneGetModelViewMatrixPtr(G));
|
|
} else {
|
|
/* stereo */
|
|
float tmpMatrix[16];
|
|
stAng = SettingGetGlobal_f(G, cSetting_stereo_angle);// * cPI / 180.f;
|
|
stShift = SettingGetGlobal_f(G, cSetting_stereo_shift);
|
|
|
|
stShift = (float) (stShift * fabs(pos.z) / 100.0);
|
|
stAng = (float) (-stAng * atan(stShift / fabs(pos.z)) / 2.f);
|
|
|
|
if(mode == 2) { /* left hand */
|
|
stAng = -stAng;
|
|
stShift = -stShift;
|
|
}
|
|
|
|
PRINTFD(G, FB_Scene)
|
|
" StereoMatrix-Debug: mode %d stAng %8.3f stShift %8.3f \n", mode, stAng, stShift
|
|
ENDFD;
|
|
identity44f(tmpMatrix);
|
|
I->modelViewMatrix = glm::mat4(1.0f);
|
|
MatrixRotateC44f(SceneGetModelViewMatrixPtr(G), stAng, 0.f, 1.f, 0.f);
|
|
MatrixTranslateC44f(tmpMatrix, pos.x + stShift, pos.y, pos.z);
|
|
MatrixMultiplyC44f(tmpMatrix, SceneGetModelViewMatrixPtr(G));
|
|
MatrixMultiplyC44f(glm::value_ptr(I->m_view.rotMatrix()), SceneGetModelViewMatrixPtr(G));
|
|
MatrixTranslateC44f(SceneGetModelViewMatrixPtr(G), -ori.x, -ori.y, -ori.z);
|
|
|
|
}
|
|
}
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if (ALWAYS_IMMEDIATE_OR(!SettingGetGlobal_b(G, cSetting_use_shaders))) {
|
|
glLoadMatrixf(SceneGetModelViewMatrixPtr(G));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
/**
|
|
* Update the scene rotation matrix (m_view.m_rotMatrix)
|
|
*
|
|
* @param angle Angle in degrees
|
|
* @param x,y,z Axis
|
|
* @param dirty Call SceneInvalidate()
|
|
*/
|
|
void SceneRotate(
|
|
PyMOLGlobals* G, float angle, float x, float y, float z, bool dirty)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float temp[16];
|
|
angle = (float) (-PI * angle / 180.0);
|
|
identity44f(temp);
|
|
MatrixRotateC44f(temp, angle, x, y, z);
|
|
MatrixMultiplyC44f(glm::value_ptr(I->m_view.rotMatrix()), temp);
|
|
I->m_view.setRotMatrix(glm::make_mat4(temp));
|
|
SceneUpdateInvMatrix(G);
|
|
if(dirty) {
|
|
SceneInvalidate(G);
|
|
}
|
|
}
|
|
|
|
void SceneRotateAxis(PyMOLGlobals* G, float angle, char axis)
|
|
{
|
|
switch (axis) {
|
|
case 'x':
|
|
SceneRotate(G, angle, 1.0f, 0.0f, 0.0f);
|
|
break;
|
|
case 'y':
|
|
SceneRotate(G, angle, 0.0f, 1.0f, 0.0f);
|
|
break;
|
|
case 'z':
|
|
SceneRotate(G, angle, 0.0f, 0.0f, 1.0f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*========================================================================*/
|
|
void SceneApplyMatrix(PyMOLGlobals * G, float *m)
|
|
{
|
|
CScene *I = G->Scene;
|
|
glm::mat4 rot;
|
|
MatrixMultiplyC44f(m, glm::value_ptr(rot));
|
|
I->m_view.setRotMatrix(rot);
|
|
SceneDirty(G);
|
|
|
|
/* glPushMatrix();
|
|
glLoadIdentity();
|
|
glMultMatrixf(m);
|
|
glMultMatrixf(glm::value_ptr(I->m_view.rotMatrix()));
|
|
glGetFloatv(GL_MODELVIEW_MATRIX,glm::value_ptr(I->m_view.rotMatrix()));
|
|
glPopMatrix(); */
|
|
}
|
|
|
|
|
|
/*========================================================================*/
|
|
void SceneScale(PyMOLGlobals * G, float scale)
|
|
{
|
|
CScene *I = G->Scene;
|
|
I->Scale *= scale;
|
|
SceneInvalidate(G);
|
|
}
|
|
|
|
void SceneZoom(PyMOLGlobals * G, float scale){
|
|
CScene *I = G->Scene;
|
|
float factor = -((I->m_view.m_clipSafe().m_front + I->m_view.m_clipSafe().m_back) / 2) * 0.1 * scale;
|
|
/* SettingGetGlobal_f(G, cSetting_mouse_wheel_scale); */
|
|
I->m_view.translate(0.0f, 0.0f, factor);
|
|
I->m_view.m_clip().m_front -= factor;
|
|
I->m_view.m_clip().m_back -= factor;
|
|
UpdateFrontBackSafe(I);
|
|
SceneInvalidate(G);
|
|
}
|
|
|
|
int SceneGetTwoSidedLighting(PyMOLGlobals * G){
|
|
return SceneGetTwoSidedLightingSettings(G, nullptr, nullptr);
|
|
}
|
|
|
|
int SceneGetTwoSidedLightingSettings(PyMOLGlobals * G,
|
|
const CSetting *set1,
|
|
const CSetting *set2) {
|
|
int two_sided_lighting = SettingGet_b(G, set1, set2, cSetting_two_sided_lighting);
|
|
if(two_sided_lighting<0) {
|
|
if(SettingGet_i(G, set1, set2, cSetting_surface_cavity_mode))
|
|
two_sided_lighting = true;
|
|
else
|
|
two_sided_lighting = false;
|
|
}
|
|
two_sided_lighting = two_sided_lighting || (SettingGet_i(G, set1, set2, cSetting_transparency_mode) ==1);
|
|
return two_sided_lighting;
|
|
}
|
|
|
|
float SceneGetDynamicLineWidth(RenderInfo * info, float line_width)
|
|
{
|
|
if(info && info->dynamic_width) {
|
|
float factor;
|
|
if(info->vertex_scale > R_SMALL4) {
|
|
factor = info->dynamic_width_factor / info->vertex_scale;
|
|
if(factor > info->dynamic_width_max)
|
|
factor = info->dynamic_width_max;
|
|
if(factor < info->dynamic_width_min) {
|
|
factor = info->dynamic_width_min;
|
|
}
|
|
} else {
|
|
factor = info->dynamic_width_max;
|
|
}
|
|
return factor * line_width;
|
|
}
|
|
return line_width;
|
|
}
|
|
|
|
float SceneGetLineWidthForCylinders(PyMOLGlobals * G, RenderInfo * info, float line_width_arg){
|
|
float line_width = SceneGetDynamicLineWidth(info, line_width_arg);
|
|
|
|
float pixel_scale_value = SettingGetGlobal_f(G, cSetting_ray_pixel_scale);
|
|
|
|
if(pixel_scale_value < 0)
|
|
pixel_scale_value = 1.0F;
|
|
/* the radius of the cylinders is the vertex_scale * ray_pixel_scale */
|
|
/* this turns out to be exactly right, but changes if the scene or user
|
|
moves */
|
|
return info->vertex_scale * pixel_scale_value * line_width / 2.f;
|
|
}
|
|
|
|
// line width arg has been processed as dynamic already
|
|
float SceneGetLineWidthForCylindersStatic(PyMOLGlobals * G, RenderInfo * info, float dynamic_line_width_arg, float line_width_arg){
|
|
float pixel_scale_value = SettingGetGlobal_f(G, cSetting_ray_pixel_scale);
|
|
if(pixel_scale_value < 0)
|
|
pixel_scale_value = 1.0F;
|
|
/* the radius of the cylinders is the vertex_scale * ray_pixel_scale */
|
|
/* this turns out to be exactly right, but changes if the scene or user
|
|
moves */
|
|
if (SceneGetStereo(G) == cStereo_openvr)
|
|
// note: that is reversion of magic dynamic line modifications in PYMOL
|
|
return pixel_scale_value * 0.07f * line_width_arg / 2.0f;
|
|
|
|
return info->vertex_scale * pixel_scale_value * dynamic_line_width_arg / 2.f;
|
|
}
|
|
|
|
void ScenePushModelViewMatrix(PyMOLGlobals * G) {
|
|
auto I = G->Scene;
|
|
I->m_ModelViewMatrixStack.push_back(I->modelViewMatrix);
|
|
}
|
|
|
|
void ScenePopModelViewMatrix(PyMOLGlobals * G, bool immediate) {
|
|
CScene *I = G->Scene;
|
|
auto& stack = I->m_ModelViewMatrixStack;
|
|
|
|
if (stack.empty()) {
|
|
printf("ERROR: depth == 0\n");
|
|
return;
|
|
}
|
|
|
|
I->modelViewMatrix = stack.back();
|
|
stack.pop_back();
|
|
|
|
#ifndef PURE_OPENGL_ES_2
|
|
if (ALWAYS_IMMEDIATE_OR(immediate)) {
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadMatrixf(SceneGetModelViewMatrixPtr(G));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
glm::mat4& SceneGetModelViewMatrix(PyMOLGlobals* G) {
|
|
return G->Scene->modelViewMatrix;
|
|
}
|
|
|
|
float* SceneGetModelViewMatrixPtr(PyMOLGlobals* G) {
|
|
auto& mat = SceneGetModelViewMatrix(G);
|
|
return glm::value_ptr(mat);
|
|
}
|
|
|
|
glm::mat4& SceneGetProjectionMatrix(PyMOLGlobals* G) {
|
|
return G->Scene->projectionMatrix;
|
|
}
|
|
|
|
float* SceneGetProjectionMatrixPtr(PyMOLGlobals* G) {
|
|
auto& mat = SceneGetProjectionMatrix(G);
|
|
return glm::value_ptr(mat);
|
|
}
|
|
|
|
void SceneSetBackgroundColorAlreadySet(PyMOLGlobals * G, int background_color_already_set){
|
|
CScene *I = G->Scene;
|
|
I->background_color_already_set = background_color_already_set;
|
|
}
|
|
int SceneGetBackgroundColorAlreadySet(PyMOLGlobals * G){
|
|
CScene *I = G->Scene;
|
|
return (I->background_color_already_set);
|
|
}
|
|
void SceneSetDoNotClearBackground(PyMOLGlobals * G, int do_not_clear){
|
|
CScene *I = G->Scene;
|
|
I->do_not_clear = do_not_clear;
|
|
}
|
|
|
|
int SceneGetDoNotClearBackground(PyMOLGlobals * G){
|
|
CScene *I = G->Scene;
|
|
return (I->do_not_clear);
|
|
}
|
|
|
|
void SceneGLClear(PyMOLGlobals * G, GLbitfield mask){
|
|
glClear(mask);
|
|
}
|
|
|
|
int SceneIncrementTextureRefreshes(PyMOLGlobals * G){
|
|
CScene *I = G->Scene;
|
|
return ++(I->n_texture_refreshes);
|
|
}
|
|
|
|
void SceneResetTextureRefreshes(PyMOLGlobals * G){
|
|
CScene *I = G->Scene;
|
|
I->n_texture_refreshes = 0;
|
|
}
|
|
|
|
void SceneGetScaledAxesAtPoint(PyMOLGlobals * G, float *pt, float *xn, float *yn)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float xn0[3] = { 1.0F, 0.0F, 0.0F };
|
|
float yn0[3] = { 0.0F, 1.0F, 0.0F };
|
|
float v_scale;
|
|
|
|
v_scale = SceneGetScreenVertexScale(G, pt);
|
|
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), xn0, xn0);
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), yn0, yn0);
|
|
scale3f(xn0, v_scale, xn);
|
|
scale3f(yn0, v_scale, yn);
|
|
}
|
|
|
|
void SceneGetScaledAxes(PyMOLGlobals * G, pymol::CObject *obj, float *xn, float *yn)
|
|
{
|
|
CScene *I = G->Scene;
|
|
float *v;
|
|
float vt[3];
|
|
float xn0[3] = { 1.0F, 0.0F, 0.0F };
|
|
float yn0[3] = { 0.0F, 1.0F, 0.0F };
|
|
float v_scale;
|
|
|
|
v = TextGetPos(G);
|
|
|
|
if(obj->TTTFlag) {
|
|
transformTTT44f3f(obj->TTT, v, vt);
|
|
} else {
|
|
copy3f(v, vt);
|
|
}
|
|
|
|
v_scale = SceneGetScreenVertexScale(G, vt);
|
|
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), xn0, xn0);
|
|
MatrixInvTransformC44fAs33f3f(glm::value_ptr(I->m_view.rotMatrix()), yn0, yn0);
|
|
scale3f(xn0, v_scale, xn);
|
|
scale3f(yn0, v_scale, yn);
|
|
}
|
|
|
|
int SceneGetCopyType(PyMOLGlobals * G) {
|
|
return G->Scene->CopyType;
|
|
}
|
|
|
|
void SceneGenerateMatrixToAnotherZFromZ(PyMOLGlobals *G, float *convMatrix, float *curpt, float *pt){
|
|
CScene *I = G->Scene;
|
|
float scaleMatrix[16];
|
|
float cscale = SceneGetExactScreenVertexScale(G, curpt);
|
|
float pscale = SceneGetExactScreenVertexScale(G, pt);
|
|
identity44f(scaleMatrix);
|
|
MatrixSetScaleC44f(scaleMatrix, pscale);
|
|
identity44f(convMatrix);
|
|
MatrixSetScaleC44f(convMatrix, 1.f/cscale);
|
|
MatrixMultiplyC44f(glm::value_ptr(I->m_view.rotMatrix()), convMatrix);
|
|
MatrixTranslateC44f(convMatrix, pt[0]-curpt[0], pt[1]-curpt[1], pt[2]-curpt[2]);
|
|
MatrixMultiplyC44f(I->InvMatrix, convMatrix);
|
|
MatrixMultiplyC44f(scaleMatrix, convMatrix);
|
|
}
|
|
|
|
void SceneAdjustZtoScreenZ(PyMOLGlobals *G, float *pos, float zarg){
|
|
CScene *I = G->Scene;
|
|
float clipRange = (I->m_view.m_clipSafe().m_back-I->m_view.m_clipSafe().m_front);
|
|
float z = (zarg + 1.f) / 2.f;
|
|
float zInPreProj = -(z * clipRange + I->m_view.m_clipSafe().m_front);
|
|
float pos4[4], tpos[4], npos[4];
|
|
float InvModMatrix[16];
|
|
copy3f(pos, pos4);
|
|
pos4[3] = 1.f;
|
|
MatrixTransformC44f4f(SceneGetModelViewMatrixPtr(G), pos4, tpos);
|
|
normalize4f(tpos);
|
|
/* NEED TO ACCOUNT FOR ORTHO */
|
|
if (SettingGetGlobal_b(G, cSetting_ortho)){
|
|
npos[0] = tpos[0];
|
|
npos[1] = tpos[1];
|
|
} else {
|
|
npos[0] = zInPreProj * tpos[0] / tpos[2];
|
|
npos[1] = zInPreProj * tpos[1] / tpos[2];
|
|
}
|
|
npos[2] = zInPreProj;
|
|
npos[3] = 1.f;
|
|
|
|
MatrixInvertC44f(SceneGetModelViewMatrixPtr(G), InvModMatrix);
|
|
MatrixTransformC44f4f(InvModMatrix, npos, npos);
|
|
normalize4f(npos);
|
|
copy3f(npos, pos);
|
|
}
|
|
|
|
/* this function takes a screen point, where z is normalized between the clipping
|
|
planes, and converts it to the world coordinates */
|
|
void SceneSetPointToWorldScreenRelative(PyMOLGlobals *G, float *pos, float *screenPt)
|
|
{
|
|
float npos[4];
|
|
float InvPmvMatrix[16];
|
|
auto extent = SceneGetExtentStereo(G);
|
|
npos[0] = (.5f + floor(screenPt[0] * extent.width)) /
|
|
extent.width; // add .5, in middle of pixels?
|
|
npos[1] = (.5f + floor(screenPt[1] * extent.height)) /
|
|
extent.height; // add .5, in middle of pixels?
|
|
npos[2] = 0.f;
|
|
npos[3] = 1.f;
|
|
MatrixInvertC44f(SceneGetPmvMatrix(G), InvPmvMatrix);
|
|
MatrixTransformC44f4f(InvPmvMatrix, npos, npos);
|
|
normalize4f(npos);
|
|
SceneAdjustZtoScreenZ(G, npos, screenPt[2]);
|
|
copy3f(npos, pos);
|
|
}
|
|
|
|
float SceneGetCurrentBackSafe(PyMOLGlobals *G){
|
|
CScene *I = G->Scene;
|
|
return (I->m_view.m_clipSafe().m_back);
|
|
}
|
|
float SceneGetCurrentFrontSafe(PyMOLGlobals *G){
|
|
CScene *I = G->Scene;
|
|
return (I->m_view.m_clipSafe().m_front);
|
|
}
|
|
|
|
/**
|
|
* Get the field-of-view width at a depth of 1.0
|
|
*/
|
|
float GetFovWidth(PyMOLGlobals * G)
|
|
{
|
|
float fov = SettingGetGlobal_f(G, cSetting_field_of_view);
|
|
return 2.f * tanf(fov * PI / 360.f);
|
|
}
|
|
|
|
void SceneInvalidatePicking(PyMOLGlobals * G){
|
|
CScene *I = G->Scene;
|
|
I->pickmgr.invalidate();
|
|
}
|
|
|
|
float SceneGetScale(PyMOLGlobals * G) {
|
|
return G->Scene->Scale;
|
|
}
|
|
|
|
void ScenePickAtomInWorld(PyMOLGlobals * G, int x, int y, float *atomWorldPos) {
|
|
CScene *I = G->Scene;
|
|
if (SceneDoXYPick(G, x, y, ClickSide::None)) {
|
|
pymol::CObject *obj = I->LastPicked.context.object;
|
|
if (obj->type != cObjectMolecule) {
|
|
return;
|
|
}
|
|
// get atom pos in Local CS
|
|
float atomPos[3];
|
|
ObjectMoleculeGetAtomTxfVertex((ObjectMolecule *)I->LastPicked.context.object, 0, I->LastPicked.src.index, atomPos);
|
|
// muptiply by molecule world matrix
|
|
MatrixTransformC44f3f(SceneGetModelViewMatrixPtr(G), atomPos, atomWorldPos);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<pymol::Image> SceneGetSharedImage(PyMOLGlobals* G)
|
|
{
|
|
return G->Scene->Image;
|
|
}
|
|
|
|
void CScene::setSceneView(const SceneView& view) {
|
|
m_view.setView(view);
|
|
SceneInvalidate(m_G);
|
|
}
|
|
SceneElem::SceneElem(std::string name_, bool drawn_)
|
|
: name(std::move(name_))
|
|
, drawn(drawn_)
|
|
{
|
|
}
|
|
|
|
void SceneSetViewport(PyMOLGlobals* G, const Rect2D& rect)
|
|
{
|
|
switch (G->GFXMgr->backend()) {
|
|
case GFXAPIBackend::OPENGL:
|
|
glViewport(rect.offset.x, rect.offset.y, rect.extent.width, rect.extent.height);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SceneSetViewport(PyMOLGlobals* G, int x, int y, int width, int height)
|
|
{
|
|
assert(width >= 0 && height >= 0);
|
|
SceneSetViewport(G, Rect2D{x, y, static_cast<std::uint32_t>(width),
|
|
static_cast<std::uint32_t>(height)});
|
|
}
|
|
|
|
Rect2D SceneGetViewport(PyMOLGlobals* G)
|
|
{
|
|
Rect2D viewport{};
|
|
int viewBuffer[4];
|
|
glGetIntegerv(GL_VIEWPORT, (GLint*) (void*) viewBuffer);
|
|
viewport.offset = Offset2D{static_cast<std::int32_t>(viewBuffer[0]),
|
|
static_cast<std::int32_t>(viewBuffer[1])};
|
|
viewport.extent = Extent2D{static_cast<std::uint32_t>(viewBuffer[2]),
|
|
static_cast<std::uint32_t>(viewBuffer[3])};
|
|
return viewport;
|
|
}
|