1 
2 /*
3    A* -------------------------------------------------------------------
4    B* This file contains source code for the PyMOL computer program
5    C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific.
6    D* -------------------------------------------------------------------
7    E* It is unlawful to modify or remove this copyright notice.
8    F* -------------------------------------------------------------------
9    G* Please see the accompanying LICENSE file for further information.
10    H* -------------------------------------------------------------------
11    I* Additional authors of this source file include:
12    -*
13    -*
14    -*
15    Z* -------------------------------------------------------------------
16 */
17 #include"os_python.h"
18 #include"os_predef.h"
19 #include"os_std.h"
20 #include"os_gl.h"
21 
22 #include"Base.h"
23 #include"OOMac.h"
24 #include"RepSphere.h"
25 #include"RepSphereImmediate.h"
26 #include"RepSphereGenerate.h"
27 #include"Color.h"
28 #include"Sphere.h"
29 #include"Map.h"
30 #include"Setting.h"
31 #include"main.h"
32 #include"Util.h"
33 #include"Feedback.h"
34 #include "ShaderMgr.h"
35 #include "Scene.h"
36 #include"CGO.h"
37 #include"ObjectMolecule.h"
38 #include "Lex.h"
39 
40 #define SPHERE_NORMAL_RANGE 6.f
41 #define SPHERE_NORMAL_RANGE2 (SPHERE_NORMAL_RANGE*SPHERE_NORMAL_RANGE)
42 
43 /* defer_builds_mode = 5 : Immediate mode for any sphere_mode
44 
45    sphere_mode :
46 
47    0) Geometry shaders (quality based on sphere_quality, default 1)
48    1) rectangular points with the same size, that can be changed with sphere_point_size
49    2) rectangles with constant size relative to vdw and scene scale (i.e., changes when zoomed)
50       maxed by a multiple of sphere_point_max_size (set it below 1 to see it influence, 3*pixel_scale max)
51    3) same as 2 but with circles
52    4) no longer available
53    5) Uses the fast ARB Shader that approximates spheres as circles
54    6-8) same as 1-3 but with normals computed from close atoms to mimic nice lighting
55    9) GLSL Shader Spheres (only when shaders are available)
56 
57  */
58 
59 static
RepSphereFree(RepSphere * I)60 void RepSphereFree(RepSphere * I)
61 {
62   if (I->primitiveCGO == I->renderCGO) {
63     I->primitiveCGO = 0;
64   }
65   CGOFree(I->primitiveCGO);
66   CGOFree(I->renderCGO);
67   CGOFree(I->spheroidCGO);
68   FreeP(I->LastColor);
69   FreeP(I->LastVisib);
70   RepPurge(&I->R);
71   OOFreeP(I);
72 }
73 
74 /* MULTI-INSTSANCE TODO:  isn't this a conflict? */
75 CShaderPrg *sphereARBShaderPrg = NULL;
76 
RenderSphereComputeFog(PyMOLGlobals * G,RenderInfo * info,float * fog_info)77 void RenderSphereComputeFog(PyMOLGlobals *G, RenderInfo *info, float *fog_info)
78 {
79   /* compute -Ze = (Wc) of fog start */
80   float nv[4];
81   nv[3] =
82     (info->front +
83      (info->back - info->front) * SettingGetGlobal_f(G, cSetting_fog_start));
84   /* compute Zc of fog start using std. perspective transformation */
85   nv[2] =
86     (nv[3] * (info->back + info->front) -
87      2 * (info->back * info->front)) / (info->back - info->front);
88   /* compute Zc/Wc to get normalized depth coordinate of fog start */
89   nv[0] = (nv[2] / nv[3]);
90   fog_info[0] = (nv[0] * 0.5) + 0.5;
91 
92   fog_info[1] = 1.0F / (1.0 - fog_info[0]);     /* effective range of fog */
93 
94 }
95 
96 #ifndef _PYMOL_NO_RAY
RepSphereRenderRay(PyMOLGlobals * G,RepSphere * I,RenderInfo * info)97 static int RepSphereRenderRay(PyMOLGlobals *G, RepSphere * I, RenderInfo * info)
98 {
99   CRay *ray = info->ray;
100   float alpha = 1.0F -
101     SettingGet_f(G, I->R.cs->Setting, I->R.obj->Setting, cSetting_sphere_transparency);
102   if(fabs(alpha - 1.0) < R_SMALL4)
103     alpha = 1.0F;
104   ray->transparentf(1.0 - alpha);
105   if (I->spheroidCGO){
106     CGORenderRay(I->spheroidCGO, ray, info, NULL, NULL, I->R.cs->Setting, I->R.obj->Setting);
107   } else {
108     CGORenderRay(I->primitiveCGO, ray, info, NULL, NULL, I->R.cs->Setting, I->R.obj->Setting);
109   }
110   ray->transparentf(0.0);
111   return true;
112 }
113 #endif
114 
RepSphereRenderPick(RepSphere * I,RenderInfo * info,int sphere_mode)115 static void RepSphereRenderPick(RepSphere * I, RenderInfo * info, int sphere_mode)
116 {
117   PyMOLGlobals *G = I->R.G;
118 
119   if (!I->renderCGO){
120     // only for sphere_mode 5, where we don't use a renderCGO (yet) ARB: immediate mode GL_QUADS
121     short use_shader = SettingGetGlobal_b(G, cSetting_sphere_use_shader) &&
122       SettingGetGlobal_b(G, cSetting_use_shaders);
123     CGO *convertcgo = CGOSimplify(I->primitiveCGO, 0, 0);
124     CGO *convertcgo2 = CGOCombineBeginEnd(convertcgo, 0);
125     if (use_shader){
126       I->renderCGO = CGOOptimizeToVBONotIndexed(convertcgo2, 0);
127       CGOFree(convertcgo2);
128     } else {
129       I->renderCGO = convertcgo2;
130     }
131     I->renderCGO->use_shader = use_shader;
132     CGOFree(convertcgo);
133   }
134   CGORenderGLPicking(I->renderCGO, info, &I->R.context, I->R.cs->Setting, I->R.obj->Setting);
135 }
136 
RepGetSphereMode(PyMOLGlobals * G,RepSphere * I,bool use_shader)137 static int RepGetSphereMode(PyMOLGlobals *G, RepSphere * I, bool use_shader){
138   int sphere_mode = SettingGet_i(G, I->R.cs->Setting,
139 				 I->R.obj->Setting,
140 				 cSetting_sphere_mode);
141   if (sphere_mode == 4) // sphere_mode 4 no longer exists, use default
142     sphere_mode = -1;
143     switch (sphere_mode) {
144     case 5:
145 #ifdef _PYMOL_ARB_SHADERS
146       if (!sphereARBShaderPrg && G->HaveGUI && G->ValidContext) {
147       sphereARBShaderPrg = CShaderPrg::NewARB(G, "sphere_arb",
148                                               G->ShaderMgr->GetShaderSource("sphere_arb_vs.vs"),
149                                               G->ShaderMgr->GetShaderSource("sphere_arb_fs.fs"));
150       }
151       if (!sphereARBShaderPrg)
152 #endif
153       {
154         PRINTFB(G, FB_ShaderMgr, FB_Warnings)
155           " Warning: ARB shaders (sphere_mode=5) not supported.\n" ENDFB(G);
156         if (!use_shader || !G->ShaderMgr->ShaderPrgExists("sphere")) {
157         sphere_mode = 9;
158         } else {
159           sphere_mode = 0;
160         }
161       }
162       break;
163     case -1:
164       sphere_mode = 9;
165     case 9:
166     if (!use_shader || !G->ShaderMgr->ShaderPrgExists("sphere")) {
167         sphere_mode = 0;
168       }
169     }
170   return sphere_mode;
171 }
172 
RepSphereRender(RepSphere * I,RenderInfo * info)173 static void RepSphereRender(RepSphere * I, RenderInfo * info)
174 {
175   CRay *ray = info->ray;
176   auto pick = info->pick;
177   PyMOLGlobals *G = I->R.G;
178   int ok = true;
179   bool use_shader = SettingGetGlobal_b(G, cSetting_sphere_use_shader) &&
180                     SettingGetGlobal_b(G, cSetting_use_shaders);
181   if(ray) {
182 #ifndef _PYMOL_NO_RAY
183     ok &= RepSphereRenderRay(G, I, info);
184 #endif
185     return;
186   }
187   int sphere_mode = RepGetSphereMode(G, I, use_shader);
188   if(G->HaveGUI && G->ValidContext) {
189     if(pick) {
190       RepSphereRenderPick(I, info, sphere_mode);
191     } else {                    /* not pick, render! */
192       if (I->spheroidCGO) {
193         CGORenderGL(I->spheroidCGO, NULL, NULL, NULL, info, &I->R);
194         return;
195       }
196 
197 #ifdef _PYMOL_ARB_SHADERS
198       if (sphere_mode == 5){
199         // we need to check sphere_mode 5 here until
200         // we implement the CGO ARB operation, since
201         // picking uses the I->renderCGO
202         RepSphere_Generate_ARB_Spheres(G, I, info);
203         return; // sphere_mode 5 does not use I->renderCGO yet
204       }
205 #endif
206       if (I->renderCGO){
207         if (I->renderCGO->use_shader != use_shader){
208           CGOFree(I->renderCGO);
209           I->renderCGO = 0;
210         } else {
211           CGORenderGL(I->renderCGO, NULL, NULL, NULL, info, &I->R);
212           return;
213         }
214       }
215       // Generate renderCGO for sphere_mode
216       switch (sphere_mode) {
217       case 0:              /* memory-efficient sphere rendering */
218         RepSphere_Generate_Triangles(G, I, info);
219         break;
220       case 9: // use GLSL impostor shader
221         RepSphere_Generate_Impostor_Spheres(G, I, info);
222         break;
223       default:
224         // sphere_modes 1,2,3,6,7,8
225         RepSphere_Generate_Point_Sprites(G, I, info, sphere_mode);
226         break;
227       }
228 
229       CHECKOK(ok, I->renderCGO);
230       if (!ok){
231         CGOFree(I->renderCGO);
232         I->R.fInvalidate(&I->R, I->R.cs, cRepInvPurge);
233         I->R.cs->Active[cRepSphere] = false;
234       }
235 
236       if (I->renderCGO)
237         CGORenderGL(I->renderCGO, NULL, NULL, NULL, info, &I->R);
238     }
239   }
240 }
241 
242 static
RepSphereSameVis(RepSphere * I,CoordSet * cs)243 int RepSphereSameVis(RepSphere * I, CoordSet * cs)
244 {
245   bool *lv;
246   int *lc;
247   int a;
248   AtomInfoType *ai;
249   if(I->LastVisib && I->LastColor) {
250     lv = I->LastVisib;
251     lc = I->LastColor;
252 
253     for(a = 0; a < cs->NIndex; a++) {
254       ai = cs->getAtomInfo(a);
255       if(*(lv++) != GET_BIT(ai->visRep, cRepSphere)) {
256         return false;
257       }
258       if(*(lc++) != ai->color) {
259         return false;
260       }
261     }
262   } else {
263     return false;
264   }
265   return true;
266 }
267 
RepSphereDetermineAtomVisibility(PyMOLGlobals * G,AtomInfoType * ati1,int cartoon_side_chain_helper,int ribbon_side_chain_helper)268 static bool RepSphereDetermineAtomVisibility(PyMOLGlobals *G,
269     AtomInfoType *ati1, int cartoon_side_chain_helper, int ribbon_side_chain_helper)
270 {
271   if (!(ati1->flags & cAtomFlag_polymer))
272     return true;
273 
274   bool sc_helper =
275     (GET_BIT(ati1->visRep, cRepCartoon) &&
276      AtomSettingGetWD(G, ati1, cSetting_cartoon_side_chain_helper, cartoon_side_chain_helper)) ||
277     (GET_BIT(ati1->visRep, cRepRibbon) &&
278      AtomSettingGetWD(G, ati1, cSetting_ribbon_side_chain_helper, ribbon_side_chain_helper));
279 
280   if (sc_helper) {
281     int prot1 = ati1->protons;
282 
283     if(prot1 == cAN_N) {
284       if(ati1->name == G->lex_const.N) {
285         if(ati1->resn != G->lex_const.PRO)
286 	  return false;
287       }
288     } else if(prot1 == cAN_O) {
289       if(ati1->name == G->lex_const.O)
290 	return false;
291     } else if(prot1 == cAN_C) {
292       if(ati1->name == G->lex_const.C)
293 	return false;
294     }
295   }
296   return true;
297 }
298 
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)299 static void RepSphereAddAtomVisInfoToStoredVC(RepSphere *I, ObjectMolecule *obj,
300     CoordSet * cs, int state, int a1, AtomInfoType *ati1, int a,
301     float sphere_scale, int sphere_color, float transp,
302     int *variable_alpha, float sphere_add)
303 {
304   PyMOLGlobals *G = cs->G;
305   float at_transp = transp;
306   int c1;
307   float vc[3];
308   const float *vcptr;
309 
310   float at_sphere_scale = AtomSettingGetWD(G, ati1, cSetting_sphere_scale, sphere_scale);
311   int at_sphere_color = AtomSettingGetWD(G, ati1, cSetting_sphere_color, sphere_color);
312 
313   if(AtomSettingGetIfDefined(G, ati1, cSetting_sphere_transparency, &at_transp))
314     *variable_alpha = true;
315 
316   int trans_pick_mode = SettingGet<int>(
317       G, cs->Setting, obj->Setting, cSetting_transparency_picking_mode);
318 
319   int pickmode = cPickableAtom;
320   if (trans_pick_mode != cTransparencyPickingModePickable &&
321       at_transp > PICKABLE_THROUGH_CUTOFF) {
322     pickmode = cPickableThrough;
323   } else if (ati1->masked) {
324     pickmode = cPickableNoPick;
325   }
326 
327   CGOPickColor(I->primitiveCGO, a1, pickmode);
328 
329   if(at_sphere_color == -1)
330     c1 = ati1->color;
331   else
332     c1 = at_sphere_color;
333   const float* v0 = cs->coordPtr(a);
334 
335   if(ColorCheckRamped(G, c1)) {
336     ColorGetRamped(G, c1, v0, vc, state);
337     vcptr = vc;
338   } else {
339     vcptr = ColorGet(G, c1);   /* save new color */
340   }
341   float alpha = 1.0F - at_transp;
342   CGOAlpha(I->primitiveCGO, alpha);
343   CGOColorv(I->primitiveCGO, vcptr);
344   float radius = obj->AtomInfo[a1].vdw * at_sphere_scale + sphere_add;
345   CGOSphere(I->primitiveCGO, v0, radius);
346 }
347 
348 /* This function is extraneous to do every time
349    we need to compute normals for RepSphere */
SphereComputeCutMultiplier(SphereRec * sr)350 static float SphereComputeCutMultiplier(SphereRec *sr){
351   int a;
352   float *dot = sr->dot[0];
353   int n_dot = sr->nDot;
354 	float cut_mult = -1.0F;
355 	  for(a = 1; a < n_dot; a++) {
356 	    float t_dot = dot_product3f(dot, dot + a * 3);
357 	    if(cut_mult < t_dot)
358 	      cut_mult = t_dot;
359 	  }
360   return cut_mult;
361 }
362 
363 /*
364  * for the spheroid implementation, this function sets color
365  * and pickcolor in the CGO given the atom idx
366  *
367  */
RepSphereCGOSetSphereColorAndPick(ObjectMolecule * obj,CoordSet * cs,CGO * cgo,int idx,int state,float transp,int sphere_color)368 static void RepSphereCGOSetSphereColorAndPick(ObjectMolecule *obj, CoordSet * cs, CGO *cgo, int idx, int state, float transp, int sphere_color){
369   PyMOLGlobals *G = obj->G;
370   int a1 = cs->IdxToAtm[idx];
371   float at_transp;
372   AtomInfoType *ati1 = obj->AtomInfo + a1;
373 
374   int at_sphere_color = AtomSettingGetWD(G, ati1, cSetting_sphere_color, sphere_color);
375 
376   if(AtomSettingGetIfDefined(G, ati1, cSetting_sphere_transparency, &at_transp)) {
377     float alpha = 1.0F - at_transp;
378     CGOAlpha(cgo, alpha);
379   }
380   int c1;
381   if(at_sphere_color == -1)
382     c1 = ati1->color;
383   else
384     c1 = at_sphere_color;
385   if(ColorCheckRamped(G, c1)) {
386     const float* v0 = cs->coordPtr(idx);
387     float color[3];
388     ColorGetRamped(G, c1, v0, color, state);
389     CGOColorv(cgo, color);
390   } else {
391     const float *color = ColorGet(G, c1);   /* save new color */
392     CGOColorv(cgo, color);
393   }
394 
395 }
396 
397 static
RepSphereGeneratespheroidCGO(ObjectMolecule * I,CoordSet * cs,SphereRec * sp,int state)398 CGO *RepSphereGeneratespheroidCGO(ObjectMolecule * I, CoordSet *cs, SphereRec *sp, int state){
399   int idx, a, b, c;
400   int *q, *s;
401   bool ok = true;
402   float spheroid_scale =
403     SettingGet_f(I->G, cs->Setting, I->Setting, cSetting_spheroid_scale);
404   int sphere_color =
405     SettingGet_color(I->G, cs->Setting, I->Setting, cSetting_sphere_color);
406   float transp = SettingGet_f(I->G, cs->Setting, I->Setting, cSetting_sphere_transparency);
407 
408   CGO *cgo = CGONew(I->G);
409   for(idx = 0; idx < cs->NIndex; idx++) {
410     float *v0 = &cs->Coord[3 * idx];
411     a = cs->IdxToAtm[idx];
412     q = sp->Sequence;
413     s = sp->StripLen;
414     RepSphereCGOSetSphereColorAndPick(I, cs, cgo, idx, state, transp, sphere_color);
415     for(b = 0; ok && b < sp->NStrip; b++) {
416       float *sphLen = cs->Spheroid.data() + (sp->nDot * a);
417       float *sphNorm = cs->SpheroidNormal.data() + (3 * sp->nDot * a);
418       CGOBegin(cgo, GL_TRIANGLE_STRIP);
419       for(c = 0; c < (*s); c++) {
420         float sphTmp, *sphTmpN = sphNorm + 3 * (*q);
421         CGONormalv(cgo, sphTmpN);
422         sphTmp = (*(sphLen + (*q))) * spheroid_scale;
423         // point
424         CGOVertex(cgo, v0[0] + sphTmp * sp->dot[*q][0],
425                        v0[1] + sphTmp * sp->dot[*q][1],
426                        v0[2] + sphTmp * sp->dot[*q][2]);
427         q++;
428       }
429       CGOEnd(cgo);
430       s++;
431       ok &= !I->G->Interrupt;
432     }
433   }
434   CGOStop(cgo);
435   if (!ok){
436     CGOFree(cgo);
437   }
438   return cgo;
439 }
440 
441 /*
442  * when normals are needed (sphere_mode 6-8),
443  * this function computes the normal for an atom
444  * and pickcolor in the CGO given the atom idx
445  *
446  */
RepSphereSetNormalForSphere(RepSphere * I,MapType * map,float * v_tmp,float * v,float cut_mult,int a,int * active,float * dot,int n_dot)447 static void RepSphereSetNormalForSphere(RepSphere *I, MapType *map, float *v_tmp,
448                                         float *v, float cut_mult, int a,
449                                         int *active, float *dot, int n_dot){
450 	  int h, k, l, b, i, j;
451 	  float v1[3];
452   int n_dot_active, *da;
453   float *vv;
454   float dst;
455 
456 	  MapLocus(map, v, &h, &k, &l);
457 	  da = active;
458 	  for(b = 0; b < n_dot; b++) {
459 	    *(da++) = b * 3;
460 	  }
461 	  n_dot_active = n_dot;
462 	  i = *(MapEStart(map, h, k, l));
463 	  if(i) {
464 	    j = map->EList[i++];
465     while(j >= 0) {
466 	      if(j != a) {
467 		vv = v_tmp + 3 * j;
468         if(within3fret(vv, v, SPHERE_NORMAL_RANGE, SPHERE_NORMAL_RANGE2, v1, &dst)) {
469 		  float cutoff = dst * cut_mult;
470 		  b = 0;
471 		  while(b < n_dot_active) {
472 		    vv = dot + active[b];
473 		    if(dot_product3f(v1, vv) > cutoff) {
474 		      n_dot_active--;
475 		      active[b] = active[n_dot_active];
476 		    }
477 		    b++;
478 		  }
479 		}
480 	      }
481 	      j = map->EList[i++];
482 	    }
483 	  }
484   float v0[3];
485 	    if(!n_dot_active) {
486 	      v0[0] = 0.0F;
487 	      v0[1] = 0.0F;
488 	      v0[2] = 1.0F;
489 	    } else {
490 	      zero3f(v0);
491 	      b = 0;
492 	      while(b < n_dot_active) {
493 		vv = dot + active[b];
494 		add3f(vv, v0, v0);
495 		b++;
496 	      }
497 	      normalize3f(v0);
498     CGONormalv(I->primitiveCGO, v0);
499 	    }
500 }
501 
RepSphereNew(CoordSet * cs,int state)502 Rep *RepSphereNew(CoordSet * cs, int state)
503 {
504   PyMOLGlobals *G = cs->G;
505   ObjectMolecule *obj;
506   int ok = true;
507   int a, a1;
508   bool *lv;
509   int *lc;
510   float sphere_scale, sphere_add = 0.f;
511   int sphere_color;
512   int cartoon_side_chain_helper = 0;
513   int ribbon_side_chain_helper = 0;
514   AtomInfoType *ati1;
515   int sphere_mode = 0;
516   bool *marked = NULL;
517   float transp;
518   int variable_alpha = false;
519   short use_shader = SettingGetGlobal_b(G, cSetting_sphere_use_shader) &&
520                      SettingGetGlobal_b(G, cSetting_use_shaders);
521   // skip if not visible
522   if(!cs->hasRep(cRepSphereBit))
523     return NULL;
524 
525   OOCalloc(G, RepSphere);
526   CHECKOK(ok, I);
527   if (!ok)
528     return NULL;
529   obj = cs->Obj;
530 
531   marked = pymol::calloc<bool>(obj->NAtom);
532   CHECKOK(ok, marked);
533   if (ok)
534     RepInit(G, &I->R);
535   I->renderCGO = NULL;
536   I->primitiveCGO = NULL;
537   if (!cs->Spheroid.empty())
538     I->spheroidCGO = RepSphereGeneratespheroidCGO(obj, cs, GetSpheroidSphereRec(G), state);
539 
540   if (ok){
541     sphere_mode = SettingGet_i(G, cs->Setting, obj->Setting, cSetting_sphere_mode);
542     if (!use_shader && (sphere_mode == 5 || sphere_mode == 9)){
543       sphere_mode = 0;
544     }
545   }
546   if (ok){
547     sphere_color =
548       SettingGet_color(G, cs->Setting, obj->Setting, cSetting_sphere_color);
549     cartoon_side_chain_helper =
550       SettingGet_b(G, cs->Setting, obj->Setting, cSetting_cartoon_side_chain_helper);
551     ribbon_side_chain_helper =
552       SettingGet_b(G, cs->Setting, obj->Setting, cSetting_ribbon_side_chain_helper);
553     transp = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_sphere_transparency);
554     sphere_scale = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_sphere_scale);
555   }
556 
557   if (ok){
558     I->R.fRender = (void (*)(struct Rep *, RenderInfo *)) RepSphereRender;
559     I->R.fFree = (void (*)(struct Rep *)) RepSphereFree;
560     I->R.fSameVis = (int (*)(struct Rep *, struct CoordSet *)) RepSphereSameVis;
561     I->R.obj = (CObject *) obj;
562     I->R.cs = cs;
563     I->R.context.object = obj;
564     I->R.context.state = state;
565   }
566   /* raytracing primitives */
567 
568   if (ok){
569     if(SettingGet_i(G, cs->Setting, obj->Setting, cSetting_sphere_solvent)) { /* are we generating a solvent surface? */
570       sphere_add = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_solvent_radius);       /* if so, get solvent radius */
571     }
572 
573     if(SettingGet_b(G, cs->Setting, obj->Setting, cSetting_pickable)) {
574       I->R.P = pymol::malloc<Pickable>(cs->NIndex + 1);
575       CHECKOK(ok, I->R.P);
576     }
577   }
578   I->primitiveCGO = CGONew(G);
579 
580   bool needNormals = (sphere_mode >= 6) && (sphere_mode < 9);
581   int nspheres = 0;
582   if (needNormals){
583     float *v_tmp = VLAlloc(float, 1024);
584   for(a = 0; ok && a < cs->NIndex; a++) {
585     a1 = cs->IdxToAtm[a];
586     ati1 = obj->AtomInfo + a1;
587     /* store temporary visibility information */
588     marked[a1] = GET_BIT(ati1->visRep,cRepSphere) &&
589         RepSphereDetermineAtomVisibility(G, ati1,
590                                          cartoon_side_chain_helper, ribbon_side_chain_helper);
591     if(marked[a1]) {
592         int cnc = nspheres * 3;
593         nspheres++;
594         VLACheck(v_tmp, float, cnc + 3);
595         copy3f(cs->coordPtr(a), &v_tmp[cnc]);
596     }
597     ok &= !G->Interrupt;
598   }
599     MapType *map = MapNew(G, SPHERE_NORMAL_RANGE, v_tmp, nspheres, NULL);
600     float cut_mult = SphereComputeCutMultiplier(G->Sphere->Sphere[1]);
601     float *dot = G->Sphere->Sphere[1]->dot[0];
602     int n_dot = G->Sphere->Sphere[1]->nDot;
603     int *active = pymol::malloc<int>(2 * n_dot);
604 
605 	ok &= MapSetupExpress(map);
606     for(a = 0; ok && a < cs->NIndex; a++) {
607       a1 = cs->IdxToAtm[a];
608       if(marked[a1]) {
609       ati1 = obj->AtomInfo + a1;
610         RepSphereSetNormalForSphere(I, map, v_tmp, &v_tmp[a * 3], cut_mult, a, active, dot, n_dot);
611         RepSphereAddAtomVisInfoToStoredVC(I, obj, cs, state, a1, ati1, a, sphere_scale, sphere_color, transp, &variable_alpha, sphere_add);
612       }
613 	ok &= !G->Interrupt;
614       }
615     FreeP(active);
616     VLAFreeP(v_tmp);
617     MapFree(map);
618   } else { // no normals necessary
619     for(a = 0; ok && a < cs->NIndex; a++) {
620       a1 = cs->IdxToAtm[a];
621       ati1 = obj->AtomInfo + a1;
622       /* store temporary visibility information */
623       marked[a1] = GET_BIT(ati1->visRep,cRepSphere) &&
624         RepSphereDetermineAtomVisibility(G, ati1,
625                                          cartoon_side_chain_helper, ribbon_side_chain_helper);
626       if(marked[a1]) {
627         nspheres++;
628         RepSphereAddAtomVisInfoToStoredVC(I, obj, cs, state, a1, ati1, a, sphere_scale, sphere_color, transp, &variable_alpha, sphere_add);
629       }
630       ok &= !G->Interrupt;
631     }
632   }
633   CGOStop(I->primitiveCGO);
634 
635   if(ok) {
636     if(!I->LastVisib)
637       I->LastVisib = pymol::malloc<bool>(cs->NIndex);
638     CHECKOK(ok, I->LastVisib);
639     if(ok && !I->LastColor)
640       I->LastColor = pymol::malloc<int>(cs->NIndex);
641     CHECKOK(ok, I->LastColor);
642     if (ok){
643       lv = I->LastVisib;
644       lc = I->LastColor;
645       obj = cs->Obj;
646       const AtomInfoType *ai2 = obj->AtomInfo.data();
647       if(sphere_color == -1){
648 	for(a = 0; a < cs->NIndex; a++) {
649           int at = cs->IdxToAtm[a];
650 	  *(lv++) = marked[at];
651 	  *(lc++) = (ai2 + at)->color;
652 	}
653       } else {
654 	for(a = 0; a < cs->NIndex; a++) {
655 	  *(lv++) = marked[cs->IdxToAtm[a]];
656 	  *(lc++) = sphere_color;
657 	}
658       }
659     }
660   }
661 
662   FreeP(marked);
663   if(nspheres == 0 || !ok) {
664     RepSphereFree(I);
665     I = NULL;
666   }
667   return (Rep *) I;
668 }
669