// qlobular.cpp // // Copyright (C) 2008, Celestia Development Team // Initial code by Dr. Fridger Schrempp // // Simulation of globular clusters, theoretical framework by // Ivan King, Astron. J. 67 (1962) 471; ibid. 71 (1966) 64 // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. #include #include #include #include #include "celestia.h" #include #include #include #include "astro.h" #include "globular.h" #include #include #include "gl.h" #include "vecgl.h" #include "render.h" #include "texture.h" #include using namespace std; static int width = 128, height = 128; static Color colorTable[256]; static const unsigned int GLOBULAR_POINTS = 8192; // Reference values ( = data base averages) of core radius and // King concentration: static const float R_c_ref = 0.83f, C_ref = 1.47f, R_mu25 = 40.32f; static const float R_end = 7.0f * R_c_ref, XI = 1.0f/sqrt(1.0f + pow(100.0f, C_ref)); // P1 determines zoom level (pixels) where individual cluster stars appear // FoV decreases (zoom increases) when P1 increases. static const float P1 = 65.0f, P2 = 0.1f; static float DiskSizeInPixels, Rr = 1.0f, Gg = 1.0f, Bb = 1.0f; static GlobularForm** globularForms = NULL; static Texture* globularTex = NULL; static Texture* centerTex = NULL; static void InitializeForms(); static GlobularForm* buildGlobularForms(float); static bool formsInitialized = false; static bool decreasing (const GBlob& b1, const GBlob& b2) { return (b1.radius_2d > b2.radius_2d); } static void GlobularTextureEval(float u, float v, float /*w*/, unsigned char *pixel) { // use a Gaussian luminosity shape for the individual stars float r = exp(- 2 * (u * u + v * v)) - 0.13533528f; // = exp(-2.0f) if (r <= 0.0f) r = 0.0f; int pixVal = (int) (r * 255.99f); #ifdef HDR_COMPRESS pixel[0] = 127; pixel[1] = 127; pixel[2] = 127; #else pixel[0] = 255;//65; pixel[1] = 255;//64; pixel[2] = 255;//65; #endif pixel[3] = pixVal; } float relStarDensity(float rho) { /* As alpha blending weight (relStarDensity) I take the theoretical number of globular stars in 2d projection within a distance rho from the center, divided by the area = PI * rho * rho. This number density of stars I normalized to 1 at rho=0. (cf. King_1962's expression (Eq.(18)). The resulting blending weight increases strongly -> 1 if the 2d number density of stars rises, i.e for rho -> 0. A typical globular concentration parameter c = C_ref (M4) is entered via XI. */ float XI2 = XI * XI; float rho2 = 1.0001f + rho * rho; //add 1e-4 as regulator near rho=0 return ((log(rho2) + 4.0f * (1.0f - sqrt(rho2)) * XI) / (rho2 - 1.0f) + XI2) / (1.0f - 2.0f * XI + XI2); } static void CenterCloudTexEval(float u, float v, float /*w*/, unsigned char *pixel) { /* Calculate central "cloud" texture with fixed /typical/ King_1962 parameters XI(c = C_ref) and r_c = R_c_ref (<=> M 4) for reasons of speed. Since it is used only for the central regime, details are not very important... */ // 2d projected King_1962 profile at center (rho = 0) float c2d = 1.0f - XI; // 2d projected King_1962 profile (Eq.(14)) float rho = R_end * sqrt(0.5f *(u * u + v * v)) / R_c_ref; float rho2 = 1.0f + rho * rho; float profile_2d = (1.0f / sqrt(rho2) - 1.0f)/c2d + 1.0f ; // absolutely no globular stars for r > r_t, where r_t = tidal radius: if (profile_2d < 0.0f) profile_2d = 0.0f; profile_2d = profile_2d * profile_2d; int pixVal = (int) (relStarDensity(rho) * profile_2d * 255.99f); #ifdef HDR_COMPRESS pixel[0] = 127; pixel[1] = 127; pixel[2] = 127; #else pixel[0] = 255;//65; pixel[1] = 255;//64; pixel[2] = 255;//65; #endif pixel[3] = pixVal; } Globular::Globular() : detail (1.0f), customTmpName (NULL), form (NULL), r_c (R_c_ref), c (C_ref) { } const char* Globular::getType() const { return "Globular"; } void Globular::setType(const std::string& /*typeStr*/) { } float Globular::getDetail() const { return detail; } void Globular::setDetail(float d) { detail = d; } string Globular::getCustomTmpName() const { if (customTmpName == NULL) return ""; else return *customTmpName; } void Globular::setCustomTmpName(const string& tmpNameStr) { if (customTmpName == NULL) customTmpName = new string(tmpNameStr); else *customTmpName = tmpNameStr; } float Globular::getCoreRadius() const { return r_c; } void Globular::setCoreRadius(float coreRadius) { r_c = coreRadius; } float Globular::getHalfMassRadius(float c, float r_c) { // Aproximation to the half-mass radius r_h [arcmin] // formula (~ 20% accuracy) return r_c * pow(10.0f, 0.6f * c - 0.4f); } float Globular::getConcentration() const { return c; } void Globular::setConcentration(float conc) { int cslot; c = conc; if (!formsInitialized) InitializeForms(); //account for the actual c values via 8 bins only, for saving time cslot = (int)floor((conc - 0.5)/0.35 + 0.5); form = globularForms[cslot]; } size_t Globular::getDescription(char* buf, size_t bufLength) const { return snprintf(buf, bufLength, _("Globular (core radius: %4.2f', King concentration: %4.2f)"), r_c, c); } GlobularForm* Globular::getForm() const { return form; } const char* Globular::getObjTypeName() const { return "globular"; } static const float RADIUS_CORRECTION = 0.025f; bool Globular::pick(const Ray3d& ray, double& distanceToPicker, double& cosAngleToBoundCenter) const { if (!isVisible()) return false; /* The ellipsoid should be slightly larger to compensate for the fact that blobs are considered points when globulars are built, but have size when they are drawn. */ Vec3d ellipsoidAxes(getRadius()*(form->scale.x + RADIUS_CORRECTION), getRadius()*(form->scale.y + RADIUS_CORRECTION), getRadius()*(form->scale.z + RADIUS_CORRECTION)); Quatf qf= getOrientation(); Quatd qd(qf.w, qf.x, qf.y, qf.z); return testIntersection(Ray3d(Point3d() + (ray.origin - getPosition()), ray.direction) * conjugate(qd).toMatrix3(), Ellipsoidd(ellipsoidAxes), distanceToPicker, cosAngleToBoundCenter); } bool Globular::load(AssociativeArray* params, const string& resPath) { if(params->getNumber("Detail", detail)) setDetail((float) detail); string customTmpName; if(params->getString("CustomTemplate", customTmpName )) setCustomTmpName(customTmpName); if(params->getNumber("CoreRadius", r_c)) setCoreRadius(r_c); if(params->getNumber("KingConcentration", c)) setConcentration(c); return DeepSkyObject::load(params, resPath); } void Globular::render(const GLContext& context, const Vec3f& offset, const Quatf& viewerOrientation, float brightness, float pixelSize) { renderGlobularPointSprites(context, offset, viewerOrientation, brightness, pixelSize); } void Globular::renderGlobularPointSprites(const GLContext&, const Vec3f& offset, const Quatf& viewerOrientation, float brightness, float pixelSize) { if (form == NULL) return; float distanceToDSO = offset.length() - getRadius(); if (distanceToDSO < 0) distanceToDSO = 0; float minimumFeatureSize = 0.5f * pixelSize * distanceToDSO; float size0 = 2 * getRadius(); // mu25 isophote radius DiskSizeInPixels = size0 / minimumFeatureSize; /* Is the globular's apparent size big enough to    be noticeable on screen? If it's not, break right here to    avoid all the overhead of the matrix transformations and GL state changes: */ if (DiskSizeInPixels < 1.0f) return; /* The factor pixelWeight affects the blended texture opacity, depending on whether resolution increases or decreases. It approaches 0 if DiskSizeInPixels >= P1 pixels, ie if the resolution increases sufficiently! */ float pixelWeight = (DiskSizeInPixels >= P1)? 1.0f/(P2 + (1.0f - P2) * DiskSizeInPixels / P1): 1.0f; if (centerTex == NULL) { centerTex = CreateProceduralTexture( width, height, GL_RGBA, CenterCloudTexEval); } assert(centerTex != NULL); if (globularTex == NULL) { globularTex = CreateProceduralTexture( width, height, GL_RGBA, GlobularTextureEval); } assert(globularTex != NULL); glEnable (GL_BLEND); glEnable (GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Mat3f viewMat = viewerOrientation.toMatrix3(); Vec3f v0 = Vec3f(-1, -1, 0) * viewMat; Vec3f v1 = Vec3f( 1, -1, 0) * viewMat; Vec3f v2 = Vec3f( 1, 1, 0) * viewMat; Vec3f v3 = Vec3f(-1, 1, 0) * viewMat; // Rescale globular clusters to actual core radius r_c: float rescale = R_c_ref / r_c; form->scale = Vec3f(rescale, rescale, rescale); Mat3f m = Mat3f::scaling(form->scale) * getOrientation().toMatrix3() * Mat3f::scaling(size0); vector* points = form->gblobs; unsigned int nPoints = (unsigned int) (points->size() * clamp(getDetail())); /* Render central cloud sprite (centerTex). It is designed to fade away when i) resolution increases ii) distance from center increases */ centerTex->bind(); float size = size0; float A = 3.0f * pixelWeight; if (A > 1.0f) A = 1.0f; float br = 2 * brightness; if (br > 1.0f) br = 1.0f; glColor4f(Rr, Gg, Bb, A * br); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex((v0 * size)); glTexCoord2f(1, 0); glVertex((v1 * size)); glTexCoord2f(1, 1); glVertex((v2 * size)); glTexCoord2f(1, 0); glVertex((v3 * size)); glEnd(); /* Next, render globular cluster via distinct "star" sprites (globularTex) for sufficiently large resolution and distance from center of globular. This RGBA texture fades away when i) resolution decreases (e.g. via automag!) ii) distance from globular center decreases */ globularTex->bind(); size = size0 / 64.0f; // Initialize size of small "star" sprites int pow2 = 128; // "Red Giants" will come from 128 biggest stars with 128 <= pow2 < 256 glBegin(GL_QUADS); for (unsigned int i = 0; i < nPoints; ++i) { GBlob b = (*points)[i]; Point3f p = b.position * m; float r_3d = b.position.distanceFromOrigin(); float r_2d = b.radius_2d; /*! Note that the [axis,angle] input in globulars.dsc transforms the * 2d star distance r_2d in the globular frame to refer to the * skyplane frame for each globular! That's what I need here. * * The [axis,angle] input will be needed anyway, when upgrading to * account for ellipticities, with corresponding inclinations and * position angles... */ if ((i & pow2) != 0) { pow2 <<= 1; size /= 1.2f; //cout<< i <<" "<< pow2 <<" "<< size0/size <