/* 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 #include #include #include #include #include #include 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(I.cur_view.extent.width / I.n_col); view.extent.height = static_cast(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((grid_col * I.cur_view.extent.width) / I.n_col); view.offset.y = static_cast( 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( (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(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(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(G->Scene->Width), static_cast(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(extent.width) / static_cast(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(I->Width), static_cast(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 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> 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(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(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(&dims)); return Extent2D{static_cast(dims[0]), static_cast(dims[1])}; } Extent2D ExtentClampByAspectRatio(Extent2D extent, const Extent2D& maxDim) { float extentAspect = static_cast(extent.width) / extent.height; if (extent.width > maxDim.width) { extent.height = static_cast(maxDim.width / extentAspect); extent.width = maxDim.width; } if (extent.height > maxDim.height) { extent.width = static_cast(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(extent.width / sceneAspectRatio); } else if (extent.height != 0 && extent.width == 0) { extent.width = static_cast(extent.height * sceneAspectRatio); } std::optional 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(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(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(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 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(); *(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(width), static_cast(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(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 &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(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(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(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(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(G, cSetting_show_alpha_checker); const float* bg_color = ColorGet(G, SettingGet(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 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(G, cSetting_show_alpha_checker); const float* bg_color = ColorGet(G, SettingGet(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 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 tmp_buffer_vec(n_word); ColorGetBkrdContColor(G, rgba, false); auto show_alpha = SettingGet(G, cSetting_show_alpha_checker); const float* bg_color = ColorGet(G, SettingGet(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(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(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 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 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 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 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(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(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 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(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 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(width), static_cast(height)}); } Rect2D SceneGetViewport(PyMOLGlobals* G) { Rect2D viewport{}; int viewBuffer[4]; glGetIntegerv(GL_VIEWPORT, (GLint*) (void*) viewBuffer); viewport.offset = Offset2D{static_cast(viewBuffer[0]), static_cast(viewBuffer[1])}; viewport.extent = Extent2D{static_cast(viewBuffer[2]), static_cast(viewBuffer[3])}; return viewport; }