/* 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: -* -* -* Z* ------------------------------------------------------------------- */ #include "os_python.h" #include "os_gl.h" #include "os_predef.h" #include "os_std.h" #include "Base.h" #include "CGO.h" #include "Color.h" #include "Err.h" #include "Feedback.h" #include "MemoryDebug.h" #include "ObjectCGO.h" #include "PConv.h" #include "Scene.h" #include "Setting.h" #include "ShaderMgr.h" #include "VFont.h" #include "main.h" static PyObject* ObjectCGOStateAsPyList(ObjectCGOState* I) { PyObject* result = nullptr; result = PyList_New(1); if (I->origCGO) PyList_SetItem(result, 0, CGOAsPyList(I->origCGO.get())); else PyList_SetItem(result, 0, PConvAutoNone(nullptr)); return (PConvAutoNone(result)); } static PyObject* ObjectCGOAllStatesAsPyList(ObjectCGO* I) { PyObject* result = nullptr; int a; result = PyList_New(I->State.size()); for (a = 0; a < I->State.size(); a++) { PyList_SetItem(result, a, ObjectCGOStateAsPyList(&I->State[a])); } return (PConvAutoNone(result)); } static int ObjectCGOStateFromPyList( PyMOLGlobals* G, ObjectCGOState* I, PyObject* list, int version) { int ok = true; int ll, pl = 0; PyObject* tmp; if (ok) ok = (list != nullptr); if (ok) ok = PyList_Check(list); if (ok) ll = PyList_Size(list); I->origCGO = nullptr; /* TO SUPPORT BACKWARDS COMPATIBILITY... Always check ll when adding new PyList_GetItem's */ if (ok && ll == 2) { tmp = PyList_GetItem(list, 0); if (CPythonVal_IsNone(tmp)) I->origCGO = nullptr; else { I->origCGO.reset(CGONewFromPyList(G, tmp, version, 1)); ok = I->origCGO != nullptr; } pl++; } if (ok && !I->origCGO) { tmp = PyList_GetItem(list, pl); if (CPythonVal_IsNone(tmp)) I->origCGO = nullptr; else { I->origCGO.reset(CGONewFromPyList(G, tmp, version, 0)); ok = I->origCGO != nullptr; } } return (ok); } static int ObjectCGOAllStatesFromPyList( ObjectCGO* I, PyObject* list, int version) { int ok = true; int a; if (ok) ok = PyList_Check(list); if (ok) { I->State.resize(PyList_Size(list), ObjectCGOState(I->G)); for (a = 0; a < I->State.size(); a++) { CPythonVal* val = CPythonVal_PyList_GetItem(I->G, list, a); ok = ObjectCGOStateFromPyList(I->G, &I->State[a], val, version); CPythonVal_Free(val); if (!ok) break; } } return (ok); } int ObjectCGONewFromPyList( PyMOLGlobals* G, PyObject* list, ObjectCGO** result, int version) { int ok = true; ObjectCGO* I = nullptr; (*result) = nullptr; if (ok) ok = (list != nullptr); if (ok) ok = PyList_Check(list); I = new ObjectCGO(G); if (ok) ok = (I != nullptr); if (ok) { CPythonVal* val = CPythonVal_PyList_GetItem(G, list, 0); ok = ObjectFromPyList(G, val, I); CPythonVal_Free(val); } if (ok) { CPythonVal* val = CPythonVal_PyList_GetItem(G, list, 2); ok = ObjectCGOAllStatesFromPyList(I, val, version); CPythonVal_Free(val); } if (ok) { (*result) = I; ObjectCGORecomputeExtent(I); } else { /* cleanup? */ } return (ok); } PyObject* ObjectCGOAsPyList(ObjectCGO* I) { PyObject* result = nullptr; result = PyList_New(3); PyList_SetItem(result, 0, ObjectAsPyList(I)); PyList_SetItem(result, 1, PyInt_FromLong(I->State.size())); PyList_SetItem(result, 2, ObjectCGOAllStatesAsPyList(I)); return (PConvAutoNone(result)); } void ObjectCGORecomputeExtent(ObjectCGO* I) { float mx[3], mn[3]; int extent_flag = false; int has_normals = 0; CGO* cgo; for (auto& state : I->State) { cgo = state.origCGO.get(); if (!cgo) { cgo = state.renderCGO.get(); } if (cgo) { if (CGOGetExtent(cgo, mn, mx)) { if (!extent_flag) { extent_flag = true; copy3f(mx, I->ExtentMax); copy3f(mn, I->ExtentMin); } else { max3f(mx, I->ExtentMax, I->ExtentMax); min3f(mn, I->ExtentMin, I->ExtentMin); } } if (!has_normals && cgo && CGOHasNormals(cgo)) { has_normals = 1; } } } I->ExtentFlag = extent_flag; SettingCheckHandle(I->G, I->Setting); SettingSet_i(I->Setting.get(), cSetting_cgo_lighting, has_normals); } /*========================================================================*/ void ObjectCGO::invalidate(cRep_t rep, cRepInv_t level, int state) { auto I = this; ObjectCGOState* sobj = nullptr; if (state < 0) { for (auto& state : I->State) { sobj = &state; sobj->renderCGO = nullptr; } } else { if ((state >= 0) && (state < I->State.size())) { // I->State[state].valid = false; sobj = &I->State[state]; sobj->renderCGO = nullptr; } } } /*========================================================================*/ void ObjectCGO::update() { for (auto& state : State) { state.renderCGO = nullptr; } SceneInvalidate(G); /* needed ? */ } /*========================================================================*/ int ObjectCGO::getNFrame() const { return State.size(); } static void ObjectCGORenderState(PyMOLGlobals* G, RenderPass pass, CRay* ray, ObjectCGO* I, RenderInfo* info, ObjectCGOState* sobj, const float* color, ObjectGadgetRamp* ramp, int use_shader, bool cgo_lighting) { if (ray) { if (sobj) { if (sobj->origCGO) { CGO *cgo = sobj->origCGO.get(), *cgo_copy = nullptr; if (cgo_lighting && CGOHasAnyTriangleVerticesWithoutNormals(cgo)) { cgo = cgo_copy = CGOGenerateNormalsForTriangles(cgo); } CGORenderRay(cgo, ray, info, color, ramp, I->Setting.get(), nullptr); CGOFree(cgo_copy); } } } else if (G->HaveGUI && G->ValidContext) { if (info->pick) { PickContext context; context.object = I; context.state = sobj - I->State.data(); CGORenderPicking( sobj->renderCGO.get(), info, &context, I->Setting.get(), nullptr); } else if (pass != RenderPass::Antialias) { bool pass_is_opaque = pass == RenderPass::Opaque; if (sobj && ((sobj->hasTransparency ^ pass_is_opaque) || (sobj->hasOpaque == pass_is_opaque))) { { CShaderPrg* shaderPrg; int two_sided_lighting = SettingGet_i( G, I->Setting.get(), nullptr, cSetting_two_sided_lighting); bool backface_cull = SettingGet_i( G, I->Setting.get(), nullptr, cSetting_backface_cull); if (two_sided_lighting < 0) { two_sided_lighting = !cgo_lighting; } two_sided_lighting &= cgo_lighting; // only set two_sided_lighting if // cgo_lighting is set #ifndef PURE_OPENGL_ES_2 if (cgo_lighting) { glEnable(GL_LIGHTING); } else { glDisable(GL_LIGHTING); } if (two_sided_lighting) { if (use_shader) glEnable(GL_VERTEX_PROGRAM_TWO_SIDE); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE); } else { if (use_shader) glDisable(GL_VERTEX_PROGRAM_TWO_SIDE); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); } #endif if (backface_cull) { glCullFace(GL_BACK); glEnable(GL_CULL_FACE); } if (use_shader) { shaderPrg = G->ShaderMgr->Enable_DefaultShader(info->pass); if (!shaderPrg) return; shaderPrg->SetLightingEnabled(cgo_lighting); shaderPrg->Set1i("two_sided_lighting_enabled", two_sided_lighting); sobj->renderCGO->use_shader = use_shader; sobj->renderCGO->debug = SettingGetGlobal_i(G, cSetting_cgo_debug); CGORender(sobj->renderCGO.get(), color, I->Setting.get(), nullptr, info, nullptr); shaderPrg->Disable(); } else { sobj->renderCGO->use_shader = use_shader; sobj->renderCGO->debug = SettingGetGlobal_i(G, cSetting_cgo_debug); CGORender(sobj->renderCGO.get(), color, I->Setting.get(), nullptr, info, nullptr); } if (backface_cull) { glDisable(GL_CULL_FACE); } #ifndef PURE_OPENGL_ES_2 if (two_sided_lighting) { if (use_shader) glDisable(GL_VERTEX_PROGRAM_TWO_SIDE); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); } if (!cgo_lighting) { glEnable(GL_LIGHTING); } #endif } } } } } static void ObjectCGOGenerateCGO(PyMOLGlobals* G, ObjectCGO* I, ObjectCGOState* sobj, bool use_shader, bool cgo_lighting, const float* color, ObjectGadgetRamp* ramp, int state) { if (sobj->renderCGO && ((use_shader ^ sobj->renderWithShaders) || (cgo_lighting ^ sobj->cgo_lighting))) { // if renderWithShaders doesn't match use_shader, clear CGO and re-generate sobj->renderCGO = nullptr; } if (!sobj->renderCGO) { float colorWithA[4]; short someLinesWithoutNormals = 0; if (color) { colorWithA[0] = color[0]; colorWithA[1] = color[1]; colorWithA[2] = color[2]; } else { colorWithA[0] = 1.f; colorWithA[1] = 1.f; colorWithA[2] = 1.f; } colorWithA[3] = 1.f - SettingGet_f(G, I->Setting.get(), nullptr, cSetting_cgo_transparency); bool hasTransparency = (colorWithA[3] < 1.f || CGOHasTransparency(sobj->origCGO.get())); bool hasOpaque = (colorWithA[3] == 1.f || CGOHasOpaque(sobj->origCGO.get())); CGO* allCylinders = nullptr; CGO* allSpheres = nullptr; CGO* allBeziers = nullptr; pymol::cache_ptr preOpt; { std::unique_ptr inputWithLighting; const CGO* inputCGO = sobj->origCGO.get(); if (cgo_lighting) { if (CGOHasAnyTriangleVerticesWithoutNormals(inputCGO)) { // we only need normals if cgo_lighting is on inputWithLighting.reset(CGOGenerateNormalsForTriangles(inputCGO)); inputCGO = inputWithLighting.get(); } someLinesWithoutNormals = CGOHasAnyLineVerticesWithoutNormals(inputCGO); if (!use_shader && someLinesWithoutNormals) { // if some lines without normals, turn lighting off on lines inputWithLighting.reset( CGOTurnLightingOnLinesOff(inputCGO, use_shader)); inputCGO = inputWithLighting.get(); } } CGO* convertcgo = CGONew(G); CGOPickColor(convertcgo, 0, cPickableGadget); CGOColorv(convertcgo, colorWithA); CGOAlpha(convertcgo, colorWithA[3]); CGOAppend(convertcgo, inputCGO); inputWithLighting.reset(); if (use_shader) { bool t_mode_3 = SettingGetGlobal_i(G, cSetting_transparency_mode) == 3; if ((t_mode_3 || !hasTransparency) && G->ShaderMgr->Get_DefaultSphereShader(RenderPass::Antialias) && G->ShaderMgr->Get_CylinderShader(RenderPass::Antialias)) { if (CGOHasCylinderOperations(convertcgo)) { allCylinders = CGONew(G); CGOEnable(allCylinders, GL_CYLINDER_SHADER); CGO* newCGO = CGOConvertShaderCylindersToCylinderShader( convertcgo, allCylinders); allCylinders->free_append(newCGO); assert(newCGO == nullptr); CGODisable(allCylinders, GL_CYLINDER_SHADER); CGOStop(allCylinders); CGO* allButCylinders = CGONew(G); CGOFilterOutCylinderOperationsInto(convertcgo, allButCylinders); CGOStop(allButCylinders); CGOFree(convertcgo); convertcgo = allButCylinders; } if (CGOHasOperationsOfType(convertcgo, CGO_SPHERE)) { CGO* allButSpheres = CGONew(G); allSpheres = CGOOptimizeSpheresToVBONonIndexed( convertcgo, 0, true, allButSpheres); if (allSpheres) { CGOFree(convertcgo); CGOStop(allButSpheres); convertcgo = allButSpheres; } else { CGOFree(allButSpheres); } } if (CGOHasBezierOperations(convertcgo)) { CGO* allButBezier = new CGO(G); allBeziers = CGOOptimizeBezier(convertcgo); CGOFilterOutBezierOperationsInto(convertcgo, allButBezier); CGOStop(allButBezier); CGOFree(convertcgo); convertcgo = allButBezier; } preOpt.reset(CGOSimplify(convertcgo, 0)); } else { preOpt.reset(CGOSimplifyNoCompress(convertcgo, 0)); } } else { preOpt.reset(CGOSimplifyNoCompress(convertcgo, 0)); } CGOFree(convertcgo); } if (ramp) { preOpt.reset( CGOColorByRamp(G, preOpt.get(), ramp, state, I->Setting.get())); } sobj->hasTransparency = hasTransparency; sobj->hasOpaque = hasOpaque; if (use_shader) { if (hasTransparency) { preOpt.reset(CGOOptimizeToVBOIndexedWithColorEmbedTransparentInfo( preOpt.get(), 0, colorWithA, false)); if (someLinesWithoutNormals) { // if some lines without normals, turn lighting off on lines CGO* convertcgo = preOpt.release(); preOpt.reset(CGOTurnLightingOnLinesOff(convertcgo, use_shader)); CGOFreeWithoutVBOs(convertcgo); } } else { preOpt.reset(CGOOptimizeToVBONotIndexed(preOpt.get(), 0)); } if (allCylinders) { preOpt->free_append(allCylinders); } if (allSpheres) { preOpt->free_append(allSpheres); } if (allBeziers) { preOpt->free_append(allBeziers); } sobj->renderCGO = std::move(preOpt); } else { assert(sobj->hasTransparency == CGOHasTransparency(preOpt.get())); assert(sobj->hasOpaque == CGOHasOpaque(preOpt.get())); if (sobj->hasTransparency) { sobj->renderCGO.reset(CGOConvertTrianglesToAlpha(preOpt.get())); sobj->renderCGO->render_alpha = 2; } else { sobj->renderCGO.reset(CGOSimplify(preOpt.get(), 0)); } } assert(allCylinders == nullptr); assert(allSpheres == nullptr); assert(allBeziers == nullptr); sobj->renderWithShaders = use_shader; sobj->cgo_lighting = cgo_lighting; } } /*========================================================================*/ void ObjectCGO::render(RenderInfo* info) { auto I = this; int state = info->state; CRay* ray = info->ray; const RenderPass pass = info->pass; ObjectCGOState* sobj = nullptr; const float* color = nullptr; bool use_shader = false, cgo_lighting = false; ObjectGadgetRamp* ramp = nullptr; use_shader = SettingGetGlobal_b(G, cSetting_cgo_use_shader) & SettingGetGlobal_b(G, cSetting_use_shaders); cgo_lighting = SettingGet_i(G, I->Setting.get(), nullptr, cSetting_cgo_lighting); ObjectPrepareContext(I, info); ramp = ColorGetRamp(G, I->Color); color = ColorGet(G, I->Color); if (I->State.empty()) return; if (pass != RenderPass::Antialias || info->ray || info->pick) { if ((I->visRep & cRepCGOBit)) { for (StateIterator iter(G, I->Setting.get(), state, I->State.size()); iter.next();) { sobj = &I->State[iter.state]; if (!sobj->origCGO) continue; if (!ray) ObjectCGOGenerateCGO( G, I, sobj, use_shader, cgo_lighting, color, ramp, iter.state); ObjectCGORenderState( G, pass, ray, I, info, sobj, color, ramp, use_shader, cgo_lighting); } } } } /*========================================================================*/ ObjectCGO::ObjectCGO(PyMOLGlobals* G) : pymol::CObject(G) { type = cObjectCGO; } ObjectCGO::ObjectCGO(const ObjectCGO& other) : pymol::CObject(other) , State(other.State) { } /*========================================================================*/ ObjectCGOState::ObjectCGOState(PyMOLGlobals* G) : G(G) { } ObjectCGOState::ObjectCGOState(const ObjectCGOState& other) : G(other.G) , renderCGO(other.renderCGO) , renderWithShaders(other.renderWithShaders) , hasTransparency(other.hasTransparency) , hasOpaque(other.hasOpaque) , cgo_lighting(other.cgo_lighting) { if (other.origCGO) { this->origCGO.reset(new CGO(other.G)); this->origCGO->append(*other.origCGO); } } /*========================================================================*/ static CGO* ObjectCGOPyListFloatToCGO(PyMOLGlobals* G, PyObject* list) { CGO* cgo = nullptr; int len; int ok = true; int result; float* raw = nullptr; if (PyList_Check(list)) { len = PConvPyListToFloatArray(list, &raw); if (len < 0) len = 0; if (raw) { if (ok) { cgo = CGONewSized(G, len); if (cgo) { result = CGOFromFloatArray(cgo, raw, len); if (result) { PRINTF " FloatToCGO: error encountered on element %d\n", result ENDF(G); } CGOStop(cgo); } } FreeP(raw); } } return (cgo); } /*========================================================================*/ static CGO* ObjectCGOFloatArrayToCGO( PyMOLGlobals* G, float* raw, int len, int quiet) { CGO* cgo = nullptr; int ok = true; int result; if (raw) { if (ok) { cgo = CGONewSized(G, len); if (cgo) { result = CGOFromFloatArray(cgo, raw, len); if (result && !quiet) { PRINTF " FloatToCGO: error encountered on element %d\n", result ENDF(G); } CGOStop(cgo); } } } return (cgo); } /*========================================================================*/ ObjectCGO* ObjectCGOFromCGO( PyMOLGlobals* G, ObjectCGO* obj, CGO* cgo, int state) { ObjectCGO* I = nullptr; int size_n; if (!obj) { I = new ObjectCGO(G); size_n = 0; } else { size_n = obj->State.size(); I = obj; } if (state < 0) state = I->State.size(); if (I->State.size() <= state) { I->State.resize(size_n + 1, ObjectCGOState(G)); } I->State[state].renderCGO = nullptr; I->State[state].origCGO = nullptr; I->State[state].origCGO.reset(cgo); if (I) { ObjectCGORecomputeExtent(I); } SceneChanged(G); SceneCountFrames(G); return (I); } /*========================================================================*/ ObjectCGO* ObjectCGONewVFontTest(PyMOLGlobals* G, const char* text, float* pos) { ObjectCGO* I = nullptr; int font_id; CGO* cgo = nullptr; float scale[2] = {1.0, 1.0}; font_id = VFontLoad(G, 1, 1, 1, true); cgo = CGONew(G); VFontWriteToCGO(G, font_id, cgo, text, pos, scale, nullptr, nullptr); I = ObjectCGOFromCGO(G, nullptr, cgo, 0); return (I); } /*========================================================================*/ /** * @pre GIL */ ObjectCGO* ObjectCGODefine( PyMOLGlobals* G, ObjectCGO* obj, PyObject* pycgo, int state) { assert(PyGILState_Check()); ObjectCGO* I = nullptr; CGO *cgo, *font_cgo; int est; int size_n; if (obj) { if (obj->type != cObjectCGO) { /* TODO: handle this */ obj = nullptr; } size_n = obj->State.size(); } if (!obj) { I = new ObjectCGO(G); size_n = 0; } else { I = obj; } if (state < 0) { state = I->State.size(); } if (I->State.size() <= state) { I->State.resize(state + 1, ObjectCGOState(G)); } I->State[state].origCGO = nullptr; if (PyList_Check(pycgo)) { if (PyList_Size(pycgo)) { if (PyFloat_Check(PyList_GetItem(pycgo, 0))) { cgo = ObjectCGOPyListFloatToCGO(G, pycgo); if (cgo) { est = CGOCheckForText(cgo); if (est) { CGOPreloadFonts(cgo); font_cgo = CGODrawText(cgo, est, nullptr); CGOFree(cgo); cgo = font_cgo; } est = CGOCheckComplex(cgo); I->State[state].origCGO.reset(cgo); } else { ErrMessage(G, "ObjectCGO", "could not parse CGO List."); } } } } if (I) { ObjectCGORecomputeExtent(I); } SceneChanged(G); SceneCountFrames(G); return (I); } ObjectCGO* ObjectCGOFromFloatArray(PyMOLGlobals* G, ObjectCGO* obj, float* array, int size, int state, int quiet) { ObjectCGO* I = nullptr; CGO *cgo, *font_cgo; int est; int size_n; if (obj) { if (obj->type != cObjectCGO) { /* TODO: handle this */ obj = nullptr; } size_n = obj->State.size(); } if (!obj) { I = new ObjectCGO(G); size_n = 0; } else { I = obj; } if (state < 0) state = I->State.size(); if (I->State.size() <= state) { I->State.resize(size_n + 1, ObjectCGOState(G)); } I->State[state].renderCGO = nullptr; I->State[state].origCGO = nullptr; cgo = ObjectCGOFloatArrayToCGO(G, array, size, quiet); if (cgo) { est = CGOCheckForText(cgo); if (est) { CGOPreloadFonts(cgo); font_cgo = CGODrawText(cgo, est, nullptr); CGOFree(cgo); cgo = font_cgo; } est = CGOCheckComplex(cgo); I->State[state].origCGO.reset(cgo); } else if (!quiet) { ErrMessage(G, "ObjectCGO", "could not parse CGO."); } if (I) { ObjectCGORecomputeExtent(I); } SceneChanged(G); SceneCountFrames(G); return (I); } pymol::CObject* ObjectCGO::clone() const { return new ObjectCGO(*this); }