/* 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"Base.h" #include"Err.h" #include"RepSphere.h" #include"RepSphereImmediate.h" #include"RepSphereGenerate.h" #include"Color.h" #include"Sphere.h" #include"Map.h" #include"Setting.h" #include"main.h" #include"Util.h" #include"Feedback.h" #include "ShaderMgr.h" #include "Scene.h" #include"CGO.h" #include"ObjectMolecule.h" #include "Lex.h" #include "CoordSet.h" #define SPHERE_NORMAL_RANGE 6.f #define SPHERE_NORMAL_RANGE2 (SPHERE_NORMAL_RANGE*SPHERE_NORMAL_RANGE) enum { cSphereModeCube = 10, cSphereModeTetrahedron = 11, }; /* defer_builds_mode = 5 : Immediate mode for any sphere_mode sphere_mode : 0) Geometry shaders (quality based on sphere_quality, default 1) 1) rectangular points with the same size, that can be changed with sphere_point_size 2) rectangles with constant size relative to vdw and scene scale (i.e., changes when zoomed) maxed by a multiple of sphere_point_max_size (set it below 1 to see it influence, 3*pixel_scale max) 3) same as 2 but with circles 4) no longer available 5) no longer available (was ARB impostor shader) 6-8) same as 1-3 but with normals computed from close atoms to mimic nice lighting 9) GLSL Shader Spheres (only when shaders are available) */ RepSphere::~RepSphere() { auto I = this; if (I->primitiveCGO == I->renderCGO) { I->primitiveCGO = 0; } CGOFree(I->primitiveCGO); CGOFree(I->renderCGO); CGOFree(I->spheroidCGO); FreeP(I->LastColor); FreeP(I->LastVisib); } void RenderSphereComputeFog(PyMOLGlobals *G, RenderInfo *info, float *fog_info) { /* compute -Ze = (Wc) of fog start */ float nv[4]; nv[3] = (info->front + (info->back - info->front) * SettingGetGlobal_f(G, cSetting_fog_start)); /* compute Zc of fog start using std. perspective transformation */ nv[2] = (nv[3] * (info->back + info->front) - 2 * (info->back * info->front)) / (info->back - info->front); /* compute Zc/Wc to get normalized depth coordinate of fog start */ nv[0] = (nv[2] / nv[3]); fog_info[0] = (nv[0] * 0.5) + 0.5; fog_info[1] = 1.0F / (1.0 - fog_info[0]); /* effective range of fog */ } #ifndef _PYMOL_NO_RAY static int RepSphereRenderRay(PyMOLGlobals *G, RepSphere * I, RenderInfo * info) { CRay *ray = info->ray; float alpha = 1.0F - SettingGet_f(G, I->cs->Setting.get(), I->obj->Setting.get(), cSetting_sphere_transparency); if(fabs(alpha - 1.0) < R_SMALL4) alpha = 1.0F; ray->transparentf(1.0 - alpha); if (I->spheroidCGO){ CGORenderRay(I->spheroidCGO, ray, info, nullptr, nullptr, I->cs->Setting.get(), I->obj->Setting.get()); } else { CGORenderRay(I->primitiveCGO, ray, info, nullptr, nullptr, I->cs->Setting.get(), I->obj->Setting.get()); } ray->transparentf(0.0); return true; } #endif static void RepSphereRenderPick(RepSphere * I, RenderInfo * info, int sphere_mode) { assert(I->renderCGO); CGORenderPicking(I->renderCGO, info, &I->context, I->cs->Setting.get(), I->obj->Setting.get()); } static int RepGetSphereMode(PyMOLGlobals *G, RepSphere * I, bool use_shader){ int sphere_mode = SettingGet_i(G, I->cs->Setting.get(), I->obj->Setting.get(), cSetting_sphere_mode); switch (sphere_mode) { case 5: // no longer exists, use default { static bool warn_once = true; if (warn_once) { PRINTFB(G, FB_ShaderMgr, FB_Warnings) " Warning: sphere_mode=5 was removed, use sphere_mode=9.\n" ENDFB(G); warn_once = false; } } case 4: // no longer exists, use default case -1: sphere_mode = 9; case 9: if (!use_shader || !G->ShaderMgr->ShaderPrgExists("sphere")) { sphere_mode = 0; } } return sphere_mode; } void RepSphere::render(RenderInfo* info) { auto I = this; CRay *ray = info->ray; auto pick = info->pick; int ok = true; bool use_shader = SettingGetGlobal_b(G, cSetting_sphere_use_shader) && SettingGetGlobal_b(G, cSetting_use_shaders); if(ray) { #ifndef _PYMOL_NO_RAY ok &= RepSphereRenderRay(G, I, info); #endif return; } int sphere_mode = RepGetSphereMode(G, I, use_shader); if(G->HaveGUI && G->ValidContext) { if(pick) { RepSphereRenderPick(I, info, sphere_mode); } else { /* not pick, render! */ if (I->spheroidCGO) { CGORender(I->spheroidCGO, nullptr, nullptr, nullptr, info, I); return; } if (I->renderCGO){ if (I->renderCGO->use_shader != use_shader){ CGOFree(I->renderCGO); I->renderCGO = 0; } else { CGORender(I->renderCGO, nullptr, nullptr, nullptr, info, I); return; } } // Generate renderCGO for sphere_mode switch (sphere_mode) { case 0: /* memory-efficient sphere rendering */ case cSphereModeCube: case cSphereModeTetrahedron: RepSphere_Generate_Triangles(G, I, info); break; case 9: // use GLSL impostor shader RepSphere_Generate_Impostor_Spheres(G, I, info); break; default: // sphere_modes 1,2,3,6,7,8 RepSphere_Generate_Point_Sprites(G, I, info, sphere_mode); break; } CHECKOK(ok, I->renderCGO); if (!ok){ CGOFree(I->renderCGO); I->invalidate(cRepInvPurge); I->cs->Active[cRepSphere] = false; } if (I->renderCGO) CGORender(I->renderCGO, nullptr, nullptr, nullptr, info, I); } } } bool RepSphere::sameVis() const { if (!LastVisib || !LastColor) { return false; } for (int idx = 0; idx < cs->NIndex; ++idx) { const auto* ai = cs->getAtomInfo(idx); if (LastVisib[idx] != GET_BIT(ai->visRep, cRepSphere)) { return false; } if (LastColor[idx] != ai->color) { return false; } } return true; } static bool RepSphereDetermineAtomVisibility(PyMOLGlobals *G, AtomInfoType *ati1, int cartoon_side_chain_helper, int ribbon_side_chain_helper) { if (!(ati1->flags & cAtomFlag_polymer)) return true; bool sc_helper = (GET_BIT(ati1->visRep, cRepCartoon) && AtomSettingGetWD(G, ati1, cSetting_cartoon_side_chain_helper, cartoon_side_chain_helper)) || (GET_BIT(ati1->visRep, cRepRibbon) && AtomSettingGetWD(G, ati1, cSetting_ribbon_side_chain_helper, ribbon_side_chain_helper)); if (sc_helper) { int prot1 = ati1->protons; if(prot1 == cAN_N) { if(ati1->name == G->lex_const.N) { if(ati1->resn != G->lex_const.PRO) return false; } } else if(prot1 == cAN_O) { if(ati1->name == G->lex_const.O) return false; } else if(prot1 == cAN_C) { if(ati1->name == G->lex_const.C) return false; } } return true; } static void RepSphereAddAtomVisInfoToStoredVC(RepSphere *I, ObjectMolecule *obj, CoordSet * cs, int state, int a1, AtomInfoType *ati1, int a, float sphere_scale, int sphere_color, float transp, int *variable_alpha, float sphere_add, int const sphere_mode) { PyMOLGlobals *G = cs->G; float at_transp = transp; int c1; float vc[3]; const float *vcptr; float at_sphere_scale = AtomSettingGetWD(G, ati1, cSetting_sphere_scale, sphere_scale); int at_sphere_color = AtomSettingGetWD(G, ati1, cSetting_sphere_color, sphere_color); if(AtomSettingGetIfDefined(G, ati1, cSetting_sphere_transparency, &at_transp)) *variable_alpha = true; int trans_pick_mode = SettingGet( G, cs->Setting.get(), obj->Setting.get(), cSetting_transparency_picking_mode); int pickmode = cPickableAtom; if (trans_pick_mode != cTransparencyPickingModePickable && at_transp > PICKABLE_THROUGH_CUTOFF) { pickmode = cPickableThrough; } else if (ati1->masked) { pickmode = cPickableNoPick; } CGOPickColor(I->primitiveCGO, a1, pickmode); if(at_sphere_color == -1) c1 = ati1->color; else c1 = at_sphere_color; const float* v0 = cs->coordPtr(a); if(ColorCheckRamped(G, c1)) { ColorGetRamped(G, c1, v0, vc, state); vcptr = vc; } else { vcptr = ColorGet(G, c1); /* save new color */ } float alpha = 1.0F - at_transp; CGOAlpha(I->primitiveCGO, alpha); CGOColorv(I->primitiveCGO, vcptr); float radius = obj->AtomInfo[a1].vdw * at_sphere_scale + sphere_add; if (alpha < 1) { I->setHasTransparency(); } CGOSphere(I->primitiveCGO, v0, radius); } /* This function is extraneous to do every time we need to compute normals for RepSphere */ static float SphereComputeCutMultiplier(SphereRec *sr){ int a; float *dot = sr->dot[0]; int n_dot = sr->nDot; float cut_mult = -1.0F; for(a = 1; a < n_dot; a++) { float t_dot = dot_product3f(dot, dot + a * 3); if(cut_mult < t_dot) cut_mult = t_dot; } return cut_mult; } /** * for the spheroid implementation, this function sets color * and pickcolor in the CGO given the atom idx * */ static void RepSphereCGOSetSphereColorAndPick(ObjectMolecule *obj, CoordSet * cs, CGO *cgo, int idx, int state, float transp, int sphere_color){ PyMOLGlobals *G = obj->G; int a1 = cs->IdxToAtm[idx]; float at_transp; AtomInfoType *ati1 = obj->AtomInfo + a1; int at_sphere_color = AtomSettingGetWD(G, ati1, cSetting_sphere_color, sphere_color); if(AtomSettingGetIfDefined(G, ati1, cSetting_sphere_transparency, &at_transp)) { float alpha = 1.0F - at_transp; CGOAlpha(cgo, alpha); } int c1; if(at_sphere_color == -1) c1 = ati1->color; else c1 = at_sphere_color; if(ColorCheckRamped(G, c1)) { const float* v0 = cs->coordPtr(idx); float color[3]; ColorGetRamped(G, c1, v0, color, state); CGOColorv(cgo, color); } else { const float *color = ColorGet(G, c1); /* save new color */ CGOColorv(cgo, color); } } static CGO *RepSphereGeneratespheroidCGO(ObjectMolecule * I, CoordSet *cs, SphereRec *sp, int state){ int idx, a, b, c; int *q, *s; bool ok = true; float spheroid_scale = SettingGet_f(I->G, cs->Setting.get(), I->Setting.get(), cSetting_spheroid_scale); int sphere_color = SettingGet_color(I->G, cs->Setting.get(), I->Setting.get(), cSetting_sphere_color); float transp = SettingGet_f(I->G, cs->Setting.get(), I->Setting.get(), cSetting_sphere_transparency); CGO *cgo = CGONew(I->G); for(idx = 0; idx < cs->NIndex; idx++) { float *v0 = &cs->Coord[3 * idx]; a = cs->IdxToAtm[idx]; q = sp->Sequence; s = sp->StripLen; RepSphereCGOSetSphereColorAndPick(I, cs, cgo, idx, state, transp, sphere_color); for(b = 0; ok && b < sp->NStrip; b++) { float *sphLen = cs->Spheroid.data() + (sp->nDot * a); float *sphNorm = cs->SpheroidNormal.data() + (3 * sp->nDot * a); CGOBegin(cgo, GL_TRIANGLE_STRIP); for(c = 0; c < (*s); c++) { float sphTmp, *sphTmpN = sphNorm + 3 * (*q); CGONormalv(cgo, sphTmpN); sphTmp = (*(sphLen + (*q))) * spheroid_scale; // point CGOVertex(cgo, v0[0] + sphTmp * sp->dot[*q][0], v0[1] + sphTmp * sp->dot[*q][1], v0[2] + sphTmp * sp->dot[*q][2]); q++; } CGOEnd(cgo); s++; ok &= !I->G->Interrupt; } } CGOStop(cgo); if (!ok){ CGOFree(cgo); } return cgo; } /** * when normals are needed (sphere_mode 6-8), * this function computes the normal for an atom * and pickcolor in the CGO given the atom idx * */ static void RepSphereSetNormalForSphere(RepSphere *I, MapType *map, float *v_tmp, float *v, float cut_mult, int a, int *active, float *dot, int n_dot){ int h, k, l, b, i, j; float v1[3]; int n_dot_active, *da; float *vv; float dst; MapLocus(map, v, &h, &k, &l); da = active; for(b = 0; b < n_dot; b++) { *(da++) = b * 3; } n_dot_active = n_dot; i = *(MapEStart(map, h, k, l)); if(i) { j = map->EList[i++]; while(j >= 0) { if(j != a) { vv = v_tmp + 3 * j; if(within3fret(vv, v, SPHERE_NORMAL_RANGE, SPHERE_NORMAL_RANGE2, v1, &dst)) { float cutoff = dst * cut_mult; b = 0; while(b < n_dot_active) { vv = dot + active[b]; if(dot_product3f(v1, vv) > cutoff) { n_dot_active--; active[b] = active[n_dot_active]; } b++; } } } j = map->EList[i++]; } } float v0[3]; if(!n_dot_active) { v0[0] = 0.0F; v0[1] = 0.0F; v0[2] = 1.0F; } else { zero3f(v0); b = 0; while(b < n_dot_active) { vv = dot + active[b]; add3f(vv, v0, v0); b++; } normalize3f(v0); CGONormalv(I->primitiveCGO, v0); } } Rep *RepSphereNew(CoordSet * cs, int state) { PyMOLGlobals *G = cs->G; ObjectMolecule *obj; int ok = true; int a, a1; bool *lv; int *lc; float sphere_scale, sphere_add = 0.f; int sphere_color; int cartoon_side_chain_helper = 0; int ribbon_side_chain_helper = 0; AtomInfoType *ati1; int sphere_mode = 0; bool *marked = nullptr; float transp; int variable_alpha = false; short use_shader = SettingGetGlobal_b(G, cSetting_sphere_use_shader) && SettingGetGlobal_b(G, cSetting_use_shaders); // skip if not visible if(!cs->hasRep(cRepSphereBit)) return nullptr; obj = cs->Obj; marked = pymol::calloc(obj->NAtom); if (!marked) return nullptr; auto I = new RepSphere(cs, state); if (!cs->Spheroid.empty()) I->spheroidCGO = RepSphereGeneratespheroidCGO(obj, cs, GetSpheroidSphereRec(G), state); if (ok){ sphere_color = SettingGet_color(G, cs->Setting.get(), obj->Setting.get(), cSetting_sphere_color); cartoon_side_chain_helper = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_side_chain_helper); ribbon_side_chain_helper = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_side_chain_helper); transp = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_sphere_transparency); sphere_scale = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_sphere_scale); } if (ok){ sphere_mode = RepGetSphereMode(G, I, use_shader); } /* raytracing primitives */ if (ok){ if(SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_sphere_solvent)) { /* are we generating a solvent surface? */ sphere_add = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_solvent_radius); /* if so, get solvent radius */ } } I->primitiveCGO = CGONew(G); bool needNormals = (sphere_mode >= 6) && (sphere_mode < 9); int nspheres = 0; if (needNormals){ float *v_tmp = VLAlloc(float, 1024); for(a = 0; ok && a < cs->NIndex; a++) { a1 = cs->IdxToAtm[a]; ati1 = obj->AtomInfo + a1; /* store temporary visibility information */ marked[a1] = GET_BIT(ati1->visRep,cRepSphere) && RepSphereDetermineAtomVisibility(G, ati1, cartoon_side_chain_helper, ribbon_side_chain_helper); if(marked[a1]) { int cnc = nspheres * 3; nspheres++; VLACheck(v_tmp, float, cnc + 3); copy3f(cs->coordPtr(a), &v_tmp[cnc]); } ok &= !G->Interrupt; } MapType *map = new MapType(G, SPHERE_NORMAL_RANGE, v_tmp, nspheres, nullptr); float cut_mult = SphereComputeCutMultiplier(G->Sphere->Sphere[1]); float *dot = G->Sphere->Sphere[1]->dot[0]; int n_dot = G->Sphere->Sphere[1]->nDot; int *active = pymol::malloc(2 * n_dot); ok &= MapSetupExpress(map); for(a = 0; ok && a < cs->NIndex; a++) { a1 = cs->IdxToAtm[a]; if(marked[a1]) { ati1 = obj->AtomInfo + a1; RepSphereSetNormalForSphere(I, map, v_tmp, &v_tmp[a * 3], cut_mult, a, active, dot, n_dot); RepSphereAddAtomVisInfoToStoredVC(I, obj, cs, state, a1, ati1, a, sphere_scale, sphere_color, transp, &variable_alpha, sphere_add, sphere_mode); } ok &= !G->Interrupt; } FreeP(active); VLAFreeP(v_tmp); MapFree(map); } else { // no normals necessary for(a = 0; ok && a < cs->NIndex; a++) { a1 = cs->IdxToAtm[a]; ati1 = obj->AtomInfo + a1; /* store temporary visibility information */ marked[a1] = GET_BIT(ati1->visRep,cRepSphere) && RepSphereDetermineAtomVisibility(G, ati1, cartoon_side_chain_helper, ribbon_side_chain_helper); if(marked[a1]) { nspheres++; RepSphereAddAtomVisInfoToStoredVC(I, obj, cs, state, a1, ati1, a, sphere_scale, sphere_color, transp, &variable_alpha, sphere_add, sphere_mode); } ok &= !G->Interrupt; } } CGOStop(I->primitiveCGO); if(ok) { if(!I->LastVisib) I->LastVisib = pymol::malloc(cs->NIndex); CHECKOK(ok, I->LastVisib); if(ok && !I->LastColor) I->LastColor = pymol::malloc(cs->NIndex); CHECKOK(ok, I->LastColor); if (ok){ lv = I->LastVisib; lc = I->LastColor; obj = cs->Obj; const AtomInfoType *ai2 = obj->AtomInfo.data(); if(sphere_color == -1){ for(a = 0; a < cs->NIndex; a++) { int at = cs->IdxToAtm[a]; *(lv++) = marked[at]; *(lc++) = (ai2 + at)->color; } } else { for(a = 0; a < cs->NIndex; a++) { *(lv++) = marked[cs->IdxToAtm[a]]; *(lc++) = sphere_color; } } } } FreeP(marked); if(nspheres == 0 || !ok) { delete I; I = nullptr; } return (Rep *) I; }