Files
pymol-open-source/layer2/ObjectCGO.cpp
Jarrett Johnson 74894e01da Generic CGO Render
2022-03-08 15:16:16 -05:00

759 lines
20 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"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<CGO> preOpt;
{
std::unique_ptr<CGO> 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);
}