globular.cpp
上传用户:center1979
上传日期:2022-07-26
资源大小:50633k
文件大小:20k
源码类别:

OpenGL

开发平台:

Visual C++

  1. // qlobular.cpp
  2. //
  3. // Copyright (C) 2008, Celestia Development Team
  4. // Initial code by Dr. Fridger Schrempp <fridger.schrempp@desy.de>
  5. //
  6. // Simulation of globular clusters, theoretical framework by  
  7. // Ivan King, Astron. J. 67 (1962) 471; ibid. 71 (1966) 64
  8. // 
  9. // This program is free software; you can redistribute it and/or
  10. // modify it under the terms of the GNU General Public License
  11. // as published by the Free Software Foundation; either version 2
  12. // of the License, or (at your option) any later version.
  13. #include <fstream>
  14. #include <algorithm>
  15. #include <cstdio>
  16. #include <cassert>
  17. #include "celestia.h"
  18. #include <celmath/mathlib.h>
  19. #include <celmath/perlin.h>
  20. #include <celmath/intersect.h>
  21. #include "astro.h"
  22. #include "globular.h"
  23. #include <celutil/util.h>
  24. #include <celutil/debug.h>
  25. #include "gl.h"
  26. #include "vecgl.h"
  27. #include "render.h"
  28. #include "texture.h"
  29. #include <math.h>
  30. using namespace std;
  31. static int cntrTexWidth = 512, cntrTexHeight = 512; 
  32. static int starTexWidth = 128, starTexHeight = 128;
  33. static Color colorTable[256];
  34. static const unsigned int GLOBULAR_POINTS  = 8192;
  35. static const float LumiShape = 3.0f, Lumi0 = exp(-LumiShape);
  36. // Reference values ( = data base averages) of core radius, King concentration 
  37. // and mu25 isophote radius:  
  38.  
  39. static const float R_c_ref = 0.83f, C_ref = 2.1f, R_mu25 = 40.32f;  
  40. // min/max c-values of globular cluster data
  41. static const float MinC = 0.50f, MaxC = 2.58f, BinWidth = (MaxC - MinC) / 8.0f + 0.02f;
  42. // P1 determines the zoom level, where individual cluster stars start to appear.
  43. // The smaller P2 (< 1), the faster stars show up when resolution increases.
  44. static const float P1 = 65.0f, P2 = 0.75f;
  45. static const float RRatio_min = pow(10.0f, 1.7f);
  46. static float CBin, RRatio, XI, DiskSizeInPixels, Rr = 1.0f, Gg = 1.0f, Bb = 1.0f;
  47. static GlobularForm** globularForms = NULL;
  48. static Texture* globularTex = NULL;
  49. static Texture* centerTex[8] = {NULL};
  50. static void InitializeForms();
  51. static GlobularForm* buildGlobularForms(float);
  52. static bool formsInitialized = false;
  53. static bool decreasing (const GBlob& b1, const GBlob& b2)
  54. {
  55. return (b1.radius_2d > b2.radius_2d);
  56. }
  57. static void GlobularTextureEval(float u, float v, float /*w*/, unsigned char *pixel)
  58. {
  59. // use an exponential luminosity shape for the individual stars
  60.     // giving sort of a halo for the brighter (i.e.bigger) stars.
  61.     float lumi = exp(- LumiShape * sqrt(u * u + v * v)) - Lumi0;
  62. if (lumi <= 0.0f)
  63. lumi = 0.0f;
  64.     int pixVal = (int) (lumi * 255.99f);
  65. #ifdef HDR_COMPRESS
  66.     pixel[0] = 127;
  67.     pixel[1] = 127;
  68.     pixel[2] = 127;
  69. #else
  70.     pixel[0] = 255;
  71.     pixel[1] = 255;
  72.     pixel[2] = 255;
  73. #endif
  74.     pixel[3] = pixVal;
  75. }
  76. float relStarDensity(float eta)
  77. {
  78. /*! As alpha blending weight (relStarDensity) I take the theoretical
  79.  *  number of globular stars in 2d projection at a distance 
  80.  *  rho = r / r_c = eta * r_t from the center (cf. King_1962's Eq.(18)), 
  81.  *  divided by the area = PI * rho * rho . This number density of stars 
  82.  *  I normalized to 1 at rho=0.   
  83.  *  The resulting blending weight increases strongly -> 1 if the 
  84.  *  2d number density of stars rises, i.e for rho -> 0. 
  85.  */    
  86.      // Since the central "cloud" is due to lack of visual resolution,
  87.  // rather than cluster morphology, we limit it's size by
  88.  // taking max(C_ref, CBin). Smaller c gives a shallower distribution!
  89.      
  90.      float rRatio = max(RRatio_min, RRatio);
  91.      float Xi = 1.0f / sqrt(1.0f + rRatio * rRatio);             
  92.      float XI2 = Xi * Xi; 
  93.  float rho2 = 1.0001f + eta * eta * rRatio * rRatio; //add 1e-4 as regulator near rho=0
  94.  return ((log(rho2) + 4.0f * (1.0f - sqrt(rho2)) * Xi) / (rho2 - 1.0f) + XI2) / (1.0f - 2.0f * Xi + XI2);
  95. }
  96. static void CenterCloudTexEval(float u, float v, float /*w*/, unsigned char *pixel)
  97. {
  98. /*! For reasons of speed, calculate central "cloud" texture only for  
  99.  *  8 bins of King_1962 concentration, c = CBin, XI(CBin), RRatio(CBin). 
  100.  */
  101.  
  102. // Skyplane projected King_1962 profile at center (rho = eta = 0):
  103. float c2d   = 1.0f - XI;
  104. float eta = sqrt(u * u + v * v);  // u,v = (-1..1)
  105. // eta^2 = u * u  + v * v = 1 is the biggest circle fitting into the quadratic
  106. // procedural texture. Hence clipping 
  107. if (eta >= 1.0f)
  108. eta  = 1.0f;
  109. // eta = 1 corresponds to tidalRadius:
  110. float rho   = eta  * RRatio;
  111. float rho2  = 1.0f + rho * rho;
  112.     // Skyplane projected King_1962 profile (Eq.(14)), vanishes for eta = 1:
  113. // i.e. absolutely no globular stars for r > tidalRadius:
  114. float profile_2d = (1.0f / sqrt(rho2) - 1.0f)/c2d + 1.0f ;
  115. profile_2d = profile_2d * profile_2d;
  116. #ifdef HDR_COMPRESS
  117.     pixel[0] = 127;
  118.     pixel[1] = 127; 
  119.     pixel[2] = 127;
  120. #else    
  121.     pixel[0] = 255;
  122.     pixel[1] = 255;
  123.     pixel[2] = 255;
  124. #endif
  125. pixel[3] = (int) (relStarDensity(eta) * profile_2d * 255.99f);
  126. }
  127. Globular::Globular() :
  128.     detail (1.0f),
  129.     customTmpName (NULL),
  130.     form (NULL),
  131. r_c (R_c_ref),
  132. c (C_ref),
  133. tidalRadius(0.0f)
  134. {
  135. recomputeTidalRadius();
  136. }
  137. unsigned int Globular::cSlot(float conc) const
  138. {
  139. // map the physical range of c, minC <= c <= maxC, 
  140. // to 8 integers (bin numbers), 0 < cSlot <= 7:
  141. if (conc <= MinC)
  142. conc  = MinC;
  143. if (conc >= MaxC)
  144. conc  = MaxC;
  145.     
  146.     return (unsigned int) floor((conc - MinC) / BinWidth);
  147. }
  148. const char* Globular::getType() const
  149. {
  150.     return "Globular";
  151. }
  152. void Globular::setType(const std::string& /*typeStr*/)
  153. {
  154. }
  155. float Globular::getDetail() const
  156. {
  157.     return detail;
  158. }
  159. void Globular::setDetail(float d)
  160. {
  161.     detail = d;
  162. }
  163. string Globular::getCustomTmpName() const
  164. {
  165.     if (customTmpName == NULL)
  166.         return "";
  167.     else
  168.         return *customTmpName;
  169. }
  170. void Globular::setCustomTmpName(const string& tmpNameStr)
  171. {
  172.     if (customTmpName == NULL)
  173.         customTmpName = new string(tmpNameStr);
  174.     else
  175.         *customTmpName = tmpNameStr;
  176. }
  177. float Globular::getCoreRadius() const
  178. {
  179.     return r_c;
  180. }
  181. void Globular::setCoreRadius(const float coreRadius)
  182. {
  183. r_c = coreRadius;
  184. recomputeTidalRadius();
  185. }
  186. float Globular::getHalfMassRadius() const
  187. {
  188. // Aproximation to the half-mass radius r_h [ly]
  189. // (~ 20% accuracy) 
  190.     
  191. return std::tan(degToRad(r_c / 60.0f)) * (float) getPosition().distanceFromOrigin() *                                 pow(10.0f, 0.6f * c - 0.4f);    
  192. }
  193. float Globular::getConcentration() const
  194. {
  195. return c;
  196. }
  197. void Globular::setConcentration(const float conc)
  198. {
  199.     c = conc;
  200.     if (!formsInitialized)
  201. InitializeForms();
  202. // For saving time, account for the c dependence via 8 bins only,
  203. form = globularForms[cSlot(conc)];
  204.     recomputeTidalRadius();
  205. }
  206. size_t Globular::getDescription(char* buf, size_t bufLength) const
  207. {
  208. return snprintf(buf, bufLength, _("Globular (core radius: %4.2f', King concentration: %4.2f)"), r_c, c);
  209. }
  210. GlobularForm* Globular::getForm() const
  211. {
  212.     return form;
  213. }
  214. const char* Globular::getObjTypeName() const
  215. {
  216.     return "globular";
  217. }
  218. static const float RADIUS_CORRECTION = 0.025f;
  219. bool Globular::pick(const Ray3d& ray,
  220.                   double& distanceToPicker,
  221.                   double& cosAngleToBoundCenter) const
  222. {
  223.     if (!isVisible())
  224.         return false;
  225. /*
  226.      * The selection ellipsoid should be slightly larger to compensate for the fact
  227.      * that blobs are considered points when globulars are built, but have size
  228.      * when they are drawn. 
  229.  */
  230. Vec3d ellipsoidAxes(getRadius() * (form->scale.x + RADIUS_CORRECTION),
  231.                         getRadius() * (form->scale.y + RADIUS_CORRECTION),
  232.                         getRadius() * (form->scale.z + RADIUS_CORRECTION));
  233.     Quatf qf= getOrientation();
  234.     Quatd qd(qf.w, qf.x, qf.y, qf.z);
  235.     return testIntersection(Ray3d(Point3d() + (ray.origin - getPosition()), ray.direction) * conjugate(qd).toMatrix3(),
  236.                             Ellipsoidd(ellipsoidAxes),
  237.                             distanceToPicker,
  238.                             cosAngleToBoundCenter);
  239. }
  240. bool Globular::load(AssociativeArray* params, const string& resPath)
  241. {
  242.     // Load the basic DSO parameters first
  243.     bool ok = DeepSkyObject::load(params, resPath);
  244.     if (!ok)
  245. return false;
  246.     if(params->getNumber("Detail", detail))
  247.      setDetail((float) detail);
  248. string customTmpName;
  249. if(params->getString("CustomTemplate", customTmpName ))
  250. setCustomTmpName(customTmpName);
  251. if(params->getNumber("CoreRadius", r_c))
  252. setCoreRadius(r_c);
  253. if(params->getNumber("KingConcentration", c))
  254. setConcentration(c);
  255.     return true;
  256. }
  257. void Globular::render(const GLContext& context,
  258.                     const Vec3f& offset,
  259.                     const Quatf& viewerOrientation,
  260.                     float brightness,
  261.                     float pixelSize)
  262. {         
  263. renderGlobularPointSprites(context, offset, viewerOrientation, brightness, pixelSize);
  264. }
  265. void Globular::renderGlobularPointSprites(const GLContext&,
  266.                                       const Vec3f& offset,
  267.                                       const Quatf& viewerOrientation,
  268.                                       float brightness,
  269.                                       float pixelSize)
  270. {
  271.     if (form == NULL)
  272.         return;
  273. float distanceToDSO = offset.length() - getRadius();
  274. if (distanceToDSO < 0)
  275.      distanceToDSO = 0;
  276. float minimumFeatureSize = 0.5f * pixelSize * distanceToDSO;    
  277. DiskSizeInPixels = getRadius() / minimumFeatureSize;
  278. /*
  279.      * Is the globular's apparent size big enough to
  280.      * be noticeable on screen? If it's not, break right here to
  281.      * avoid all the overhead of the matrix transformations and
  282.  * GL state changes: 
  283.  */
  284. if (DiskSizeInPixels < 1.0f)
  285. return;     
  286.     /*
  287.  * When resolution (zoom) varies, the blended texture opacity is controlled by the 
  288.  * factor 'pixelWeight'. At low resolution, the latter starts at 1, but tends to 0,  
  289.      * if the resolution increases sufficiently (DiskSizeInPixels >= P1 pixels)! 
  290.  * The smaller P2 (<1), the faster pixelWeight -> 0, for DiskSizeInPixels >= P1.
  291.  */
  292. float pixelWeight = (DiskSizeInPixels >= P1)? 1.0f/(P2 + (1.0f - P2) * DiskSizeInPixels / P1): 1.0f;
  293.     // Use same 8 c-bins as in globularForms below!        
  294.     
  295.     unsigned int ic = cSlot(c); 
  296.     CBin = MinC + ((float) ic + 0.5f) * BinWidth; // center value of (ic+1)th c-bin
  297. RRatio = pow(10.0f, CBin); 
  298.     XI = 1.0f / sqrt(1.0f + RRatio * RRatio);           
  299.     
  300.     if(centerTex[ic] == NULL)
  301. {
  302. centerTex[ic] = CreateProceduralTexture( cntrTexWidth, cntrTexHeight, GL_RGBA, CenterCloudTexEval);
  303. }
  304. assert(centerTex[ic] != NULL);
  305. if (globularTex == NULL)
  306.     {
  307.         globularTex = CreateProceduralTexture( starTexWidth, starTexHeight, GL_RGBA,
  308.                                                GlobularTextureEval);
  309.     }
  310.     assert(globularTex != NULL);
  311. glEnable (GL_BLEND);
  312. glEnable (GL_TEXTURE_2D);
  313. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  314.     
  315.     Mat3f viewMat = viewerOrientation.toMatrix3();
  316.     Vec3f v0 = Vec3f(-1, -1, 0) * viewMat;
  317.     Vec3f v1 = Vec3f( 1, -1, 0) * viewMat;
  318.     Vec3f v2 = Vec3f( 1,  1, 0) * viewMat;
  319.     Vec3f v3 = Vec3f(-1,  1, 0) * viewMat;
  320. float tidalSize = 2 * tidalRadius;
  321. Mat3f m =
  322.         Mat3f::scaling(form->scale) * getOrientation().toMatrix3() * 
  323. Mat3f::scaling(tidalSize);
  324.      
  325.     vector<GBlob>* points = form->gblobs;
  326.     unsigned int nPoints = 
  327.  (unsigned int) (points->size() * clamp(getDetail()));
  328.    
  329. /* Render central cloud sprite (centerTex). It fades away when
  330.  * distance from center or resolution increases sufficiently. 
  331.  */
  332. centerTex[ic]->bind();
  333.     
  334. float br = 2 * brightness;
  335. glColor4f(Rr, Gg, Bb, min(br * pixelWeight, 1.0f));
  336. glBegin(GL_QUADS);
  337. glTexCoord2f(0, 0);          glVertex(v0 * tidalSize);
  338.     glTexCoord2f(1, 0);          glVertex(v1 * tidalSize);
  339.     glTexCoord2f(1, 1);          glVertex(v2 * tidalSize);
  340.     glTexCoord2f(0, 1);          glVertex(v3 * tidalSize);   
  341. glEnd();      
  342.  
  343. /*! Next, render globular cluster via distinct "star" sprites (globularTex) 
  344.  * for sufficiently large resolution and distance from center of globular.
  345.  *  
  346.  * This RGBA texture fades away when resolution decreases (e.g. via automag!), 
  347.  * or when distance from globular center decreases.
  348.  */
  349. globularTex->bind();
  350. int pow2 = 128;      // Associate "Red Giants" with the 128 biggest star-sprites  
  351. float starSize =  br * 0.5f; // Maximal size of star sprites -> "Red Giants"
  352. float clipDistance = 100.0f; // observer distance [ly] from globular, where we 
  353.                          // start "morphing" the star-sprite sizes towards 
  354.                      // their physical values
  355. glBegin(GL_QUADS);
  356.     
  357. for (unsigned int i = 0; i < nPoints; ++i)
  358. {     
  359. GBlob         b  = (*points)[i];
  360. Point3f       p  = b.position * m;
  361. float    eta_2d  = b.radius_2d;
  362. /*! Note that the [axis,angle] input in globulars.dsc transforms the 
  363.  *  2d projected star distance r_2d in the globular frame to refer to the 
  364.  *  skyplane frame for each globular! That's what I need here.
  365.      *
  366.      *  The [axis,angle] input will be needed anyway, when upgrading to 
  367.  *  account for  ellipticities, with corresponding  inclinations and 
  368.  *  position angles...
  369.      */
  370. if ((i & pow2) != 0)
  371. {      
  372. pow2    <<=   1;    
  373. starSize /= 1.25f;
  374.    
  375. if (starSize < minimumFeatureSize)
  376.             break;
  377. float obsDistanceToStarRatio = (p + offset).distanceFromOrigin() / clipDistance;
  378. float saveSize = starSize;
  379. if (obsDistanceToStarRatio < 1.0f)
  380. {  
  381. // "Morph" the star-sprite sizes at close observer distance such that
  382. // the overdense globular core is dissolved upon closing in.
  383.             
  384. starSize = starSize * min(obsDistanceToStarRatio, 1.0f);     
  385. }
  386.          
  387. /* Colors of normal globular stars are given by color profile.
  388.  * Associate orange "Red Giant" stars with the largest sprite
  389.  * sizes (while pow2 = 128).
  390.  */
  391. Color col  = (pow2 < 256)? colorTable[255]: colorTable[b.colorIndex];
  392. glColor4f(col.red(), col.green(), col.blue(), 
  393.       min(br * (1.0f - pixelWeight * relStarDensity(eta_2d)), 1.0f));
  394.    
  395. glTexCoord2f(0, 0);          glVertex(p + (v0 * starSize));
  396. glTexCoord2f(1, 0);          glVertex(p + (v1 * starSize));
  397. glTexCoord2f(1, 1);          glVertex(p + (v2 * starSize));
  398. glTexCoord2f(0, 1);          glVertex(p + (v3 * starSize));
  399.             
  400. starSize = saveSize;
  401. }
  402. glEnd();
  403. }
  404. unsigned int Globular::getRenderMask() const
  405. {
  406.     return Renderer::ShowGlobulars;
  407. }
  408. unsigned int Globular::getLabelMask() const
  409. {
  410.     return Renderer::GlobularLabels;
  411. }
  412. void Globular::recomputeTidalRadius()
  413. {
  414. // Convert the core radius from arcminutes to light years
  415. // Compute the tidal radius in light years
  416.         
  417. float coreRadiusLy = std::tan(degToRad(r_c / 60.0f)) * (float) getPosition().distanceFromOrigin();
  418.     tidalRadius = coreRadiusLy * std::pow(10.0f, c);     
  419. }
  420. GlobularForm* buildGlobularForms(float c)
  421. {
  422. GBlob b;
  423. vector<GBlob>* globularPoints = new vector<GBlob>;
  424. float rRatio = pow(10.0f, c); //  = r_t / r_c
  425. float prob;
  426. float cc =  1.0f + rRatio * rRatio;
  427. unsigned int i = 0, k = 0;
  428. // Value of King_1962 luminosity profile at center:
  429. float prob0 = sqrt(cc) - 1.0f;
  430. /*! Generate the globular star distribution randomly, according 
  431.  *  to the  King_1962 surface density profile f(r), eq.(14).
  432.  *
  433.  *  rho = r / r_c = eta r_t / r_c, 0 <= eta <= 1,  
  434.     *  coreRadius r_c, tidalRadius r_t, King concentration c = log10(r_t/r_c). 
  435.  */
  436. while (i < GLOBULAR_POINTS)
  437. {
  438.     /*!  
  439.  *  Use a combination of the Inverse Transform method and 
  440.  *  Von Neumann's Acceptance-Rejection method for generating sprite stars 
  441.  *  with eta distributed according to the exact King luminosity profile.    
  442.  *
  443.  * This algorithm leads to almost 100% efficiency for all values of  
  444.  * parameters and variables!
  445.  */
  446. float uu = Mathf::frand();
  447. /* First step: eta distributed as inverse power distribution (~1/Z^2) 
  448.  * that majorizes the exact King profile. Compute eta in terms of uniformly 
  449.  * distributed variable uu! Normalization to 1 for eta -> 0.
  450.          */ 
  451. float eta = tan(uu *atan(rRatio))/rRatio;
  452. float rho = eta * rRatio;
  453. float  cH = 1.0f/(1.0f + rho * rho);
  454.         float   Z = sqrt((1.0f + rho * rho)/cc); // scaling variable
  455.     // Express King_1962 profile in terms of the UNIVERSAL variable 0 < Z <= 1,
  456. prob = (1.0f - 1.0f / Z) / prob0;
  457. prob = prob * prob;
  458.        /* Second step: Use Acceptance-Rejection method (Von Neumann) for
  459.  * correcting the power distribution of eta into the exact, 
  460.  * desired King form 'prob'!
  461.  */
  462.         k++;
  463. if (Mathf::frand() < prob / cH)
  464. {
  465.     /* Generate 3d points of globular cluster stars in polar coordinates:
  466.  * Distribution in eta (<=> r) according to King's profile. 
  467.      * Uniform distribution on any spherical surface for given eta. 
  468.  * Note: u = cos(phi) must be used as a stochastic variable to get uniformity in angle!
  469.  */
  470. float u = Mathf::sfrand(); 
  471. float theta = 2 * (float) PI * Mathf::frand();
  472. float sthetu2 = sin(theta) * sqrt(1.0f - u * u);
  473. // x,y,z points within -0.5..+0.5, as required for consistency: 
  474. b.position = 0.5f * Point3f(eta * sqrt(1.0f - u * u) * cos(theta), eta * sthetu2 , eta * u);
  475.             
  476. /* 
  477.  * Note: 2d projection in x-z plane, according to Celestia's
  478.  * conventions! Hence...
  479.  */
  480. b.radius_2d = eta * sqrt(1.0f - sthetu2 * sthetu2);
  481. /* For now, implement only a generic spectrum for normal cluster
  482.  * stars, modelled from Hubble photo of M80. 
  483.  * Blue Stragglers are qualitatively accounted for...
  484.      * assume color index poportional to Z as function of which the King profile
  485.  * becomes universal!
  486.  */
  487.   
  488. b.colorIndex  = (unsigned int) (Z * 254);
  489.             
  490.     globularPoints->push_back(b);
  491. i++;
  492. }
  493. }
  494. // Check for efficiency of sprite-star generation => close to 100 %!
  495. //cout << "c =  "<< c <<"  i =  " << i - 1 <<"  k =  " << k - 1 <<                                                       "  Efficiency:  " << 100.0f * i / (float)k<<"%" << endl;
  496. GlobularForm* globularForm  = new GlobularForm();
  497. globularForm->gblobs        = globularPoints;
  498. globularForm->scale         = Vec3f(1.0f, 1.0f, 1.0f);
  499. return globularForm;
  500. }
  501. void InitializeForms()
  502. {
  503. // Build RGB color table, using hue, saturation, value as input.
  504. // Hue in degrees.
  505. // Location of hue transition and saturation peak in color index space: 
  506. int i0 = 36, i_satmax = 16; 
  507. // Width of hue transition in color index space: 
  508. int i_width = 3;
  509. float sat_l = 0.08f, sat_h = 0.1f, hue_r = 27.0f, hue_b = 220.0f;
  510. // Red Giant star color: i = 255:
  511. // -------------------------------
  512. // Convert hue, saturation and value to RGB
  513. DeepSkyObject::hsv2rgb(&Rr, &Gg, &Bb, 25.0f, 0.65f, 1.0f);
  514. colorTable[255]  = Color(Rr, Gg, Bb);
  515. // normal stars: i < 255, generic color profile for now, improve later
  516. // --------------------------------------------------------------------
  517.     // Convert hue, saturation, value to RGB
  518. for (int i = 254; i >=0; i--)
  519.     {
  520. // simple qualitative saturation profile:
  521. // i_satmax is value of i where sat = sat_h + sat_l maximal
  522.     float x = (float) i / (float) i_satmax, x2 = x ;
  523. float sat = sat_l + 2 * sat_h /(x2 + 1.0f / x2);
  524. // Fast transition from hue_r to hue_b at i = i0 within a width
  525. // i_width in color index space:
  526. float hue = hue_r + 0.5f * (hue_b - hue_r) * (std::tanh((float)(i - i0) / (float) i_width) + 1.0f);
  527. DeepSkyObject::hsv2rgb(&Rr, &Gg, &Bb, hue, sat, 0.85f);
  528. colorTable[i]  = Color(Rr, Gg, Bb);
  529. }
  530. // Define globularForms corresponding to 8 different bins of King concentration c 
  531.    
  532. globularForms = new GlobularForm*[8];   
  533. for (unsigned int ic  = 0; ic <= 7; ++ic)
  534.     {
  535.         float CBin = MinC + ((float) ic + 0.5f) * BinWidth;
  536. globularForms[ic] = buildGlobularForms(CBin); 
  537. }
  538. formsInitialized = true;
  539. }