Files
pymol-open-source/layer2/RepRibbon.cpp
2026-03-10 22:28:14 -04:00

693 lines
21 KiB
C++

/*
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific.
D* -------------------------------------------------------------------
E* It is unlawful to modify or remove this copyright notice.
F* -------------------------------------------------------------------
G* Please see the accompanying LICENSE file for further information.
H* -------------------------------------------------------------------
I* Additional authors of this source file include:
-*
-*
-*
Z* -------------------------------------------------------------------
*/
#include"os_python.h"
#include"os_predef.h"
#include"os_std.h"
#include"os_gl.h"
#include"Base.h"
#include"Err.h"
#include"RepRibbon.h"
#include"Color.h"
#include"Setting.h"
#include"Word.h"
#include"Scene.h"
#include"main.h"
#include"Feedback.h"
#include"ShaderMgr.h"
#include"CGO.h"
#include "Lex.h"
#include "CoordSet.h"
struct RepRibbon : Rep {
using Rep::Rep;
~RepRibbon() override;
cRep_t type() const override { return cRepRibbon; }
void render(RenderInfo* info) override;
float ribbon_width;
float radius;
CGO *shaderCGO;
CGO *primitiveCGO;
bool shaderCGO_has_cylinders;
};
#include"ObjectMolecule.h"
RepRibbon::~RepRibbon()
{
CGOFree(primitiveCGO);
CGOFree(shaderCGO);
}
static void RepRibbonRenderRay(RepRibbon* I, RenderInfo* info) {
auto ray = info->ray;
CGORenderRay(I->primitiveCGO, ray, info, nullptr, nullptr, I->cs->Setting.get(), I->obj->Setting.get());
}
static bool RepRibbonCGOGenerate(RepRibbon* I) {
bool ok = true;
CGO* convertcgo = nullptr;
auto G = I->G;
I->shaderCGO = CGONew(G);
CHECKOK(ok, I->shaderCGO);
bool ribbon_as_cylinders = SettingGet<bool>(G, cSetting_render_as_cylinders) &&
SettingGet<bool>(G, I->cs->Setting.get(),
I->obj->Setting.get(),
cSetting_ribbon_as_cylinders);
if (ok)
I->shaderCGO->use_shader = true;
if (ok)
ok &= CGOResetNormal(I->shaderCGO, true);
if (ribbon_as_cylinders) {
if (ok)
ok &= CGOEnable(I->shaderCGO, GL_CYLINDER_SHADER);
if (ok)
ok &= CGOSpecial(I->shaderCGO, CYLINDER_WIDTH_FOR_RIBBONS);
convertcgo = CGOConvertLinesToCylinderShader(I->primitiveCGO, I->shaderCGO);
if (ok)
ok &= CGOAppendNoStop(I->shaderCGO, convertcgo);
if (ok)
ok &= CGODisable(I->shaderCGO, GL_CYLINDER_SHADER);
if (ok)
ok &= CGOStop(I->shaderCGO);
} else {
int trilines = SettingGetGlobal_b(G, cSetting_trilines);
int shader = trilines ? GL_TRILINES_SHADER : GL_LINE_SHADER;
if (ok)
ok &= CGOEnable(I->shaderCGO, shader);
if (ok)
ok &= CGODisable(I->shaderCGO, CGO_GL_LIGHTING);
if (trilines) {
if (ok)
ok &= CGOSpecial(I->shaderCGO, LINEWIDTH_DYNAMIC_WITH_SCALE_RIBBON);
convertcgo = CGOConvertToTrilinesShader(I->primitiveCGO, I->shaderCGO);
} else {
convertcgo = CGOConvertToLinesShader(I->primitiveCGO, I->shaderCGO);
}
if (ok)
ok &= CGOAppendNoStop(I->shaderCGO, convertcgo);
if (ok)
ok &= CGODisable(I->shaderCGO, shader);
if (ok)
ok &= CGOStop(I->shaderCGO);
}
I->shaderCGO_has_cylinders = ribbon_as_cylinders;
CGOFreeWithoutVBOs(convertcgo);
I->shaderCGO->use_shader = true;
return ok;
}
static void RepRibbonRenderRaster(RepRibbon* I, RenderInfo* info)
{
auto G = I->G;
bool ribbon_as_cylinders = SettingGet<bool>(G, cSetting_render_as_cylinders) &&
SettingGet<bool>(G, I->cs->Setting.get(),
I->obj->Setting.get(),
cSetting_ribbon_as_cylinders);
if (I->shaderCGO && (ribbon_as_cylinders ^ I->shaderCGO_has_cylinders)) {
CGOFree(I->shaderCGO);
I->shaderCGO = nullptr;
}
if (!I->shaderCGO) {
RepRibbonCGOGenerate(I);
}
CGORender(I->shaderCGO, nullptr, I->cs->Setting.get(), I->obj->Setting.get(),
info, I);
}
static void RepRibbonPick(RepRibbon* I, RenderInfo* info)
{
CGORenderPicking(I->shaderCGO ? I->shaderCGO : I->primitiveCGO, info,
&I->context, I->cs->Setting.get(), I->obj->Setting.get(), I);
}
static void RepRibbonRenderImmediate(RepRibbon* I, RenderInfo* info)
{
CGORender(I->primitiveCGO, nullptr, I->cs->Setting.get(),
I->obj->Setting.get(), info, I);
}
void RepRibbon::render(RenderInfo* info)
{
auto I = this;
CRay *ray = info->ray;
auto pick = info->pick;
if (ray) {
RepRibbonRenderRay(I, info);
return;
}
if (!(G->HaveGUI && G->ValidContext)) {
return;
}
if (pick) {
RepRibbonPick(I, info);
return;
}
bool use_shader = SettingGet<bool>(G, cSetting_ribbon_use_shader) &&
SettingGet<bool>(G, cSetting_use_shaders);
if (!use_shader && I->shaderCGO) {
CGOFree(I->shaderCGO);
I->shaderCGO = nullptr;
}
if (use_shader) {
RepRibbonRenderRaster(I, info);
return;
}
RepRibbonRenderImmediate(I, info);
}
Rep *RepRibbonNew(CoordSet * cs, int state)
{
PyMOLGlobals *G = cs->G;
ObjectMolecule *obj;
int a, b, a1, a2, *i, *s, *at, *seg, nAt, *atp;
float *v, *v1, *v2, *v3;
float *pv = nullptr;
float *dv = nullptr;
float *nv = nullptr;
float *tv = nullptr;
float f0, f1, f2, f3, f4;
float *d;
float *dl = nullptr;
int nSeg;
int sampling;
float power_a = 5;
float power_b = 5;
float throw_;
float dev;
int trace, trace_mode;
int ribbon_color;
int na_mode;
AtomInfoType *ai, *last_ai = nullptr;
AtomInfoType *trailing_O3p_ai = nullptr, *leading_O5p_ai = nullptr;
int trailing_O3p_a = 0, leading_O5p_a = 0, leading_O5p_a1 = 0;
// skip if not visible
if(!cs->hasRep(cRepRibbonBit))
return nullptr;
auto I = new RepRibbon(cs, state);
obj = cs->Obj;
power_a = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_power);
power_b = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_power_b);
throw_ = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_throw);
int trace_ostate = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_trace_atoms);
trace_mode = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_trace_atoms_mode);
na_mode =
SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_nucleic_acid_mode);
ribbon_color =
SettingGet_color(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_color);
sampling = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_sampling);
if(sampling < 1)
sampling = 1;
I->radius = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_radius);
I->ribbon_width = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_width);
/* find all of the CA points */
auto const nAtIndex = cs->getNIndex(); // was NAtIndex
at = pymol::malloc<int>(nAtIndex * 2);
pv = pymol::malloc<float>(nAtIndex * 6);
seg = pymol::malloc<int>(nAtIndex * 2);
i = at;
v = pv;
s = seg;
nAt = 0;
nSeg = 0;
a2 = -1;
for(a1 = 0; a1 < obj->NAtom; ++a1) {
a = cs->atmToIdx(a1);
if(a >= 0) {
ai = obj->AtomInfo + a1;
if(GET_BIT(obj->AtomInfo[a1].visRep,cRepRibbon)) {
trace = AtomSettingGetWD(G, ai, cSetting_ribbon_trace_atoms, trace_ostate);
if(trace || ((obj->AtomInfo[a1].protons == cAN_C) &&
(WordMatchExact(G, G->lex_const.CA, obj->AtomInfo[a1].name, true)) &&
!AtomInfoSameResidueP(G, last_ai, ai))) {
PRINTFD(G, FB_RepRibbon)
" RepRibbon: found atom in %d; a1 %d a2 %d\n", obj->AtomInfo[a1].resv, a1, a2
ENDFD;
// auto-detect CA-only models
if (!ai->bonded)
trace = true;
if(a2 >= 0) {
if(trace) {
if(!AtomInfoSequential
(G, obj->AtomInfo + a2, obj->AtomInfo + a1, trace_mode))
a2 = -1;
} else {
if(!ObjectMoleculeCheckBondSep(obj, a1, a2, 3)) /* CA->N->C->CA = 3 bonds */
a2 = -1;
}
}
PRINTFD(G, FB_RepRibbon)
" RepRibbon: found atom in %d; a1 %d a2 %d\n", obj->AtomInfo[a1].resv, a1, a2
ENDFD;
last_ai = ai;
if(a2 < 0)
nSeg++;
*(s++) = nSeg;
nAt++;
*(i++) = a;
v1 = cs->coordPtr(a);
*(v++) = *(v1++);
*(v++) = *(v1++);
*(v++) = *(v1++);
a2 = a1;
} else if((((na_mode != 1) &&
AtomInfoIsNucBackboneTrace(LexStr(G, ai->name), ai->protons)) ||
((na_mode == 1) && (ai->protons == cAN_C) &&
(WordMatchExact(G, "C4*", LexStr(G, ai->name), 1) ||
WordMatchExact(G, "C4'", LexStr(G, ai->name), 1)))) &&
!AtomInfoSameResidueP(G, last_ai, ai)) {
if(a2 >= 0) {
if(!ObjectMoleculeCheckBondSep(obj, a1, a2, 6)) { /* six bonds between phosphates */
if(trailing_O3p_ai && ((na_mode == 2) || (na_mode == 4))) {
/* 3' end of nucleic acid */
*(s++) = nSeg;
nAt++;
*(i++) = trailing_O3p_a;
v1 = cs->coordPtr(trailing_O3p_a);
*(v++) = *(v1++);
*(v++) = *(v1++);
*(v++) = *(v1++);
}
a2 = -1;
}
}
trailing_O3p_ai = nullptr;
if(leading_O5p_ai && (a2 < 0) && ((na_mode == 3) || (na_mode == 4))) {
if((!AtomInfoSameResidueP(G, ai, leading_O5p_ai)) &&
ObjectMoleculeCheckBondSep(obj, a1, leading_O5p_a1, 5)) {
nSeg++;
*(s++) = nSeg;
nAt++;
*(i++) = leading_O5p_a;
v1 = cs->coordPtr(leading_O5p_a);
*(v++) = *(v1++);
*(v++) = *(v1++);
*(v++) = *(v1++);
a2 = leading_O5p_a1;
}
}
leading_O5p_ai = nullptr;
last_ai = ai;
if(a2 < 0)
nSeg++;
*(s++) = nSeg;
nAt++;
*(i++) = a;
v1 = cs->coordPtr(a);
*(v++) = *(v1++);
*(v++) = *(v1++);
*(v++) = *(v1++);
a2 = a1;
} else if((a2 >= 0) &&
last_ai &&
(ai->protons == cAN_O) &&
(last_ai->protons == cAN_P) &&
((na_mode == 2) || (na_mode == 4)) &&
(WordMatchExact(G, "O3'", LexStr(G, ai->name), 1) ||
WordMatchExact(G, "O3*", LexStr(G, ai->name), 1)) &&
AtomInfoSameResidueP(G, last_ai, ai) &&
ObjectMoleculeCheckBondSep(obj, a1, a2, 5)) {
trailing_O3p_ai = ai;
trailing_O3p_a = a;
} else if((ai->protons == cAN_O) &&
((na_mode == 3) || (na_mode == 4)) &&
(WordMatchExact(G, "O5'", LexStr(G, ai->name), 1) ||
WordMatchExact(G, "O5*", LexStr(G, ai->name), 1))) {
leading_O5p_ai = ai;
leading_O5p_a = a;
leading_O5p_a1 = a1;
}
}
}
}
if(trailing_O3p_ai && ((na_mode == 2) || (na_mode == 4))) {
/* 3' end of nucleic acid */
*(s++) = nSeg;
nAt++;
*(i++) = trailing_O3p_a;
v1 = cs->coordPtr(trailing_O3p_a);
*(v++) = *(v1++);
*(v++) = *(v1++);
*(v++) = *(v1++);
}
PRINTFD(G, FB_RepRibbon)
" RepRibbon: nAt %d\n", nAt ENDFD;
if(nAt) {
/* compute differences and normals */
s = seg;
v = pv;
dv = pymol::malloc<float>(nAt * 6);
nv = pymol::malloc<float>(nAt * 6);
dl = pymol::malloc<float>(nAt * 2);
v1 = dv;
v2 = nv;
d = dl;
for(a = 0; a < (nAt - 1); a++) {
if(*s == *(s + 1)) {
float d_1;
subtract3f(v + 3, v, v1);
*d = (float) length3f(v1);
if(*d > R_SMALL4) {
d_1 = 1.0F / (*d);
scale3f(v1, d_1, v2);
} else if(a) {
copy3f(v2 - 3, v2);
} else {
zero3f(v2);
}
}
d++;
v += 3;
v1 += 3;
v2 += 3;
s++;
}
/* compute tangents */
s = seg;
v = nv;
tv = pymol::malloc<float>(nAt * 6 + 6);
v1 = tv;
*(v1++) = *(v++); /* first segment */
*(v1++) = *(v++);
*(v1++) = *(v++);
s++;
for(a = 1; a < (nAt - 1); a++) {
if((*s == *(s - 1)) && (*s == *(s + 1))) {
add3f(v, (v - 3), v1);
normalize3f(v1);
} else if(*s == *(s - 1)) {
*(v1) = *(v - 3); /* end a segment */
*(v1 + 1) = *(v - 2);
*(v1 + 2) = *(v - 1);
} else if(*s == *(s + 1)) {
*(v1) = *(v); /* new segment */
*(v1 + 1) = *(v + 1);
*(v1 + 2) = *(v + 2);
}
v += 3;
v1 += 3;
s++;
}
*(v1++) = *(v - 3); /* last segment */
*(v1++) = *(v - 2);
*(v1++) = *(v - 1);
}
/* okay, we now have enough info to generate smooth interpolations */
I->shaderCGO = 0;
I->primitiveCGO = 0;
if(nAt) {
v1 = pv; /* points */
v2 = tv; /* tangents */
v3 = dv; /* direction vector */
d = dl;
s = seg;
atp = at;
I->primitiveCGO = CGONew(G);
CGOSpecialWithArg(I->primitiveCGO, LINE_LIGHTING, 0.f);
float alpha = 1.f - SettingGet_f(G, nullptr, I->obj->Setting.get(), cSetting_ribbon_transparency);
if(fabs(alpha-1.0) < R_SMALL4)
alpha = 1.0F;
CGOAlpha(I->primitiveCGO, alpha); // would be good to set these at render time instead
CGOSpecial(I->primitiveCGO, LINEWIDTH_DYNAMIC_WITH_SCALE_RIBBON);
if (alpha < 1) {
I->setHasTransparency();
}
// This is required for immediate mode rendering
CGOBegin(I->primitiveCGO, GL_LINES);
auto const get_color = [&](AtomInfoType const* ai) {
auto c = AtomSettingGetWD(G, ai, cSetting_ribbon_color, ribbon_color);
return (c != cColorDefault) ? c : ai->color;
};
float origV1[14], origV2[14], *origV = origV2;
bool origVis1 = false;
for(a = 0; a < (nAt - 1); a++) {
if(*s == *(s + 1)) {
int const atm[2] = {cs->IdxToAtm[atp[0]], cs->IdxToAtm[atp[1]]};
AtomInfoType const *ai1 = obj->AtomInfo + atm[0];
AtomInfoType const *ai2 = obj->AtomInfo + atm[1];
int const color[2] = {get_color(ai1), get_color(ai2)};
int const atmpk[2] = {
ai1->masked ? cPickableNoPick : cPickableAtom,
ai2->masked ? cPickableNoPick : cPickableAtom,
};
dev = throw_ * (*d);
for(b = 0; b < sampling; b++) { /* needs optimization */
origVis1 = !origVis1;
if (origVis1){
origV = origV1;
} else {
origV = origV2;
}
size_t const i1 = (b + 0) * 2 < sampling ? 0 : 1;
size_t const i2 = (b + 1) * 2 > sampling ? 1 : 0;
assert(i1 <= i2);
/*
14 floats per v:
v[0] = index1
v[1-3] = color1
v[4-6] = vertex1
v[7] = index2
v[8-10] = color2
v[11-13] = vertex2
*/
f0 = ((float) b) / sampling; /* fraction of completion */
f0 = smooth(f0, power_a); /* bias sampling towards the center of the curve */
// start of line/cylinder
f1 = 1.0F - f0;
f2 = smooth(f0, power_b);
f3 = smooth(f1, power_b);
f4 = dev * f2 * f3; /* displacement magnitude */
/* store vertex1 v[4-6] */
origV[4] = f1 * v1[0] + f0 * v1[3] + f4 * (f3 * v2[0] - f2 * v2[3]);
origV[5] = f1 * v1[1] + f0 * v1[4] + f4 * (f3 * v2[1] - f2 * v2[4]);
origV[6] = f1 * v1[2] + f0 * v1[5] + f4 * (f3 * v2[2] - f2 * v2[5]);
bool isRamped =
ColorGetCheckRamped(G, color[i1], origV + 4, origV + 1, state);
f0 = ((float) b + 1) / sampling;
f0 = smooth(f0, power_a);
/* end of line/cylinder */
f1 = 1.0F - f0;
f2 = smooth(f0, power_b);
f3 = smooth(f1, power_b);
f4 = dev * f2 * f3; /* displacement magnitude */
/* store vertex2 v[11-13] */
origV[11] = f1 * v1[0] + f0 * v1[3] + f4 * (f3 * v2[0] - f2 * v2[3]);
origV[12] = f1 * v1[1] + f0 * v1[4] + f4 * (f3 * v2[1] - f2 * v2[4]);
origV[13] = f1 * v1[2] + f0 * v1[5] + f4 * (f3 * v2[2] - f2 * v2[5]);
if (ColorGetCheckRamped(G, color[i2], origV + 11, origV + 8, state)) {
isRamped = true;
}
CGOPickColor(I->primitiveCGO, atm[i1], atmpk[i1]);
CGOColorv(I->primitiveCGO, origV + 1);
I->primitiveCGO->add<cgo::draw::splitline>(origV + 4, origV + 11,
origV + 8, atm[i2], atmpk[i2], isRamped, false, false);
}
}
v1 += 3;
v2 += 3;
v3 += 3;
d++;
atp += 1;
s++;
}
CGOEnd(I->primitiveCGO);
CGOSpecialWithArg(I->primitiveCGO, LINE_LIGHTING, 1.f);
CGOStop(I->primitiveCGO);
FreeP(dv);
FreeP(dl);
FreeP(tv);
FreeP(nv);
} else {
delete I;
I = nullptr;
}
FreeP(at);
FreeP(seg);
FreeP(pv);
return (Rep *) I;
}
void RepRibbonRenderImmediate(CoordSet * cs, RenderInfo * info)
{
#ifndef PURE_OPENGL_ES_2
/* performance optimized to provide a simple C-alpha trace -- no smoothing */
PyMOLGlobals *G = cs->G;
if(info->ray || info->pick || (!(G->HaveGUI && G->ValidContext)))
return;
else {
ObjectMolecule *obj = cs->Obj;
int active = false;
int nAtIndex = obj->NAtom;
const AtomInfoType *obj_AtomInfo = obj->AtomInfo.data();
const AtomInfoType *ai, *last_ai = nullptr;
int trace, trace_ostate =
SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_trace_atoms);
int trace_mode =
SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_trace_atoms_mode);
int na_mode =
SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_nucleic_acid_mode);
float ribbon_width =
SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_ribbon_width);
int a1, a2 = -1;
int color, last_color = -9;
glLineWidth(ribbon_width);
SceneResetNormal(G, true);
if(!info->line_lighting)
glDisable(GL_LIGHTING);
glBegin(GL_LINE_STRIP);
for(a1 = 0; a1 < nAtIndex; a1++) {
auto a = cs->atmToIdx(a1);
if(a >= 0) {
ai = obj_AtomInfo + a1;
if(GET_BIT(ai->visRep,cRepRibbon)) {
trace = AtomSettingGetWD(G, ai, cSetting_ribbon_trace_atoms, trace_ostate);
if(trace || ((ai->protons == cAN_C) &&
(WordMatchExact(G, G->lex_const.CA, ai->name, true)) &&
!AtomInfoSameResidueP(G, last_ai, ai))) {
if(a2 >= 0) {
if(trace) {
if(!AtomInfoSequential
(G, obj_AtomInfo + a2, obj_AtomInfo + a1, trace_mode))
a2 = -1;
} else {
if(!ObjectMoleculeCheckBondSep(obj, a1, a2, 3)) /* CA->N->C->CA = 3 bonds */
a2 = -1;
}
}
if(a2 == -1) {
glEnd();
glBegin(GL_LINE_STRIP);
}
color = ai->color;
if(color != last_color) {
last_color = color;
glColor3fv(ColorGet(G, color));
}
glVertex3fv(cs->coordPtr(a));
active = true;
last_ai = ai;
a2 = a1;
} else if((((na_mode != 1) &&
AtomInfoIsNucBackboneTrace(LexStr(G, ai->name), ai->protons)) ||
((na_mode == 1) && (ai->protons == cAN_C) &&
(WordMatchExact(G, "C4*", LexStr(G, ai->name), 1) ||
WordMatchExact(G, "C4'", LexStr(G, ai->name), 1)))) &&
!AtomInfoSameResidueP(G, last_ai, ai)) {
if(a2 >= 0) {
if(!ObjectMoleculeCheckBondSep(obj, a1, a2, 6)) { /* six bonds between phosphates */
a2 = -1;
}
}
if(a2 == -1) {
glEnd();
glBegin(GL_LINE_STRIP);
}
color = ai->color;
if(color != last_color) {
last_color = color;
glColor3fv(ColorGet(G, color));
}
glVertex3fv(cs->coordPtr(a));
active = true;
last_ai = ai;
a2 = a1;
}
}
}
}
glEnd();
glEnable(GL_LIGHTING);
if(!active)
cs->Active[cRepRibbon] = false;
}
#endif
}