/* 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_predef.h" #include"os_std.h" #include"os_gl.h" #include"Err.h" #include"ObjectCGO.h" #include"Base.h" #include"MemoryDebug.h" #include"CGO.h" #include"Scene.h" #include"Setting.h" #include"PConv.h" #include"main.h" #include"Color.h" #include"VFont.h" #include"ShaderMgr.h" #include "Feedback.h" static PyObject *ObjectCGOStateAsPyList(ObjectCGOState * I) { PyObject *result = NULL; result = PyList_New(1); if(I->origCGO) PyList_SetItem(result, 0, CGOAsPyList(I->origCGO.get())); else PyList_SetItem(result, 0, PConvAutoNone(NULL)); return (PConvAutoNone(result)); } static PyObject *ObjectCGOAllStatesAsPyList(ObjectCGO * I) { PyObject *result = NULL; 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 != NULL); 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); if(!ok) break; } } return (ok); } int ObjectCGONewFromPyList(PyMOLGlobals * G, PyObject * list, ObjectCGO ** result, int version) { int ok = true; ObjectCGO *I = NULL; (*result) = NULL; if(ok) ok = (list != NULL); if(ok) ok = PyList_Check(list); I = new ObjectCGO(G); if(ok) ok = (I != NULL); if(ok){ auto *val = PyList_GetItem(list, 0); ok = ObjectFromPyList(G, val, I); } 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 = NULL; 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 = NULL; if (cgo_lighting && CGOHasAnyTriangleVerticesWithoutNormals(cgo)) { cgo = cgo_copy = CGOGenerateNormalsForTriangles(cgo); } CGORenderRay(cgo, ray, info, color, ramp, I->Setting.get(), NULL); 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(), NULL, cSetting_two_sided_lighting); bool backface_cull = SettingGet_i(G, I->Setting.get(), NULL, 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(), NULL, info, NULL); 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(), NULL, info, NULL); } 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(), NULL, 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 = NULL; CGO *allSpheres = NULL; 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); } } 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); } 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); 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 = NULL; const float *color = NULL; bool use_shader = false, cgo_lighting = false; ObjectGadgetRamp *ramp = NULL; use_shader = SettingGetGlobal_b(G, cSetting_cgo_use_shader) & SettingGetGlobal_b(G, cSetting_use_shaders); cgo_lighting = SettingGet_i(G, I->Setting.get(), NULL, 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 = NULL; int len; int ok = true; int result; float *raw = NULL; 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 = NULL; 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 = NULL; 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 = NULL; int font_id; CGO *cgo = NULL; 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, NULL, NULL); I = ObjectCGOFromCGO(G, NULL, cgo, 0); return (I); } /*========================================================================*/ /** * @pre GIL */ ObjectCGO *ObjectCGODefine(PyMOLGlobals * G, ObjectCGO * obj, PyObject * pycgo, int state) { assert(PyGILState_Check()); ObjectCGO *I = NULL; CGO *cgo, *font_cgo; int est; int size_n; if(obj) { if(obj->type != cObjectCGO){ /* TODO: handle this */ obj = NULL; } 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, NULL); 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 = NULL; CGO *cgo, *font_cgo; int est; int size_n; if(obj) { if(obj->type != cObjectCGO){ /* TODO: handle this */ obj = NULL; } 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, NULL); 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); }