1 // galaxy.cpp
2 //
3 // Copyright (C) 2001-2005, Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 
10 #include <cstring>
11 #include <fstream>
12 #include <algorithm>
13 #include <cstdio>
14 #include <cassert>
15 #include "celestia.h"
16 #include <celmath/mathlib.h>
17 #include <celmath/perlin.h>
18 #include <celmath/intersect.h>
19 #include "astro.h"
20 #include "galaxy.h"
21 #include <celutil/util.h>
22 #include <celutil/debug.h>
23 #include "gl.h"
24 #include "vecgl.h"
25 #include "render.h"
26 #include "texture.h"
27 
28 using namespace std;
29 
30 
31 static int width = 128, height = 128;
32 static Color colorTable[256];
33 static const unsigned int GALAXY_POINTS  = 3500;
34 
35 static bool formsInitialized = false;
36 
37 static GalacticForm** spiralForms     = NULL;
38 static GalacticForm** ellipticalForms = NULL;
39 static GalacticForm*  irregularForm   = NULL;
40 
41 static Texture* galaxyTex = NULL;
42 
43 static void InitializeForms();
44 static GalacticForm* buildGalacticForms(const std::string& filename);
45 
46 float Galaxy::lightGain  = 0.0f;
47 
operator <(const Blob & b1,const Blob & b2)48 bool operator < (const Blob& b1, const Blob& b2)
49 {
50   return (b1.position.distanceFromOrigin() < b2.position.distanceFromOrigin());
51 }
52 
53 struct GalaxyTypeName
54 {
55     const char* name;
56     Galaxy::GalaxyType type;
57 };
58 
59 static GalaxyTypeName GalaxyTypeNames[] =
60 {
61     { "S0",  Galaxy::S0 },
62     { "Sa",  Galaxy::Sa },
63     { "Sb",  Galaxy::Sb },
64     { "Sc",  Galaxy::Sc },
65     { "SBa", Galaxy::SBa },
66     { "SBb", Galaxy::SBb },
67     { "SBc", Galaxy::SBc },
68     { "E0",  Galaxy::E0 },
69     { "E1",  Galaxy::E1 },
70     { "E2",  Galaxy::E2 },
71     { "E3",  Galaxy::E3 },
72     { "E4",  Galaxy::E4 },
73     { "E5",  Galaxy::E5 },
74     { "E6",  Galaxy::E6 },
75     { "E7",  Galaxy::E7 },
76     { "Irr", Galaxy::Irr },
77 };
78 
79 
GalaxyTextureEval(float u,float v,float,unsigned char * pixel)80 static void GalaxyTextureEval(float u, float v, float /*w*/, unsigned char *pixel)
81 {
82     float r = 0.9f - (float) sqrt(u * u + v * v );
83     if (r < 0)
84         r = 0;
85 
86     int pixVal = (int) (r * 255.99f);
87 #ifdef HDR_COMPRESS
88     pixel[0] = 127;
89     pixel[1] = 127;
90     pixel[2] = 127;
91 #else
92     pixel[0] = 255;//65;
93     pixel[1] = 255;//64;
94     pixel[2] = 255;//65;
95 #endif
96     pixel[3] = pixVal;
97 }
98 
99 
Galaxy()100 Galaxy::Galaxy() :
101     detail(1.0f),
102     customTmpName(NULL),
103     form(NULL)
104 {
105 }
106 
107 
getDetail() const108 float Galaxy::getDetail() const
109 {
110     return detail;
111 }
112 
113 
setDetail(float d)114 void Galaxy::setDetail(float d)
115 {
116     detail = d;
117 }
118 
119 
setCustomTmpName(const string & tmpNameStr)120 void Galaxy::setCustomTmpName(const string& tmpNameStr)
121 {
122     if (customTmpName == NULL)
123         customTmpName = new string(tmpNameStr);
124     else
125         *customTmpName = tmpNameStr;
126 }
127 
128 
getCustomTmpName() const129 string Galaxy::getCustomTmpName() const
130 {
131     if (customTmpName == NULL)
132         return "";
133     else
134         return *customTmpName;
135 }
136 
137 
getType() const138 const char* Galaxy::getType() const
139 {
140     return GalaxyTypeNames[(int) type].name;
141 }
142 
143 
setType(const string & typeStr)144 void Galaxy::setType(const string& typeStr)
145 {
146     type = Galaxy::Irr;
147     for (int i = 0; i < (int) (sizeof(GalaxyTypeNames) / sizeof(GalaxyTypeNames[0])); ++i)
148     {
149         if (GalaxyTypeNames[i].name == typeStr)
150         {
151             type = GalaxyTypeNames[i].type;
152             break;
153         }
154     }
155 
156     if (!formsInitialized)
157         InitializeForms();
158 
159 	if (customTmpName != NULL)
160 	{
161 			form = buildGalacticForms("models/" + *customTmpName);
162 	}
163 	else
164 	{
165 		switch (type)
166 		{
167 		case S0:
168 		case Sa:
169 		case Sb:
170 		case Sc:
171 		case SBa:
172 		case SBb:
173 		case SBc:
174 			form = spiralForms[type - S0];
175 			break;
176 		case E0:
177 		case E1:
178 		case E2:
179 		case E3:
180 		case E4:
181 		case E5:
182 		case E6:
183 		case E7:
184 			form = ellipticalForms[type - E0];
185 			//form = NULL;
186 			break;
187 		case Irr:
188 			form = irregularForm;
189 			break;
190 		}
191 	}
192 }
193 
194 
getDescription(char * buf,size_t bufLength) const195 size_t Galaxy::getDescription(char* buf, size_t bufLength) const
196 {
197     return snprintf(buf, bufLength, _("Galaxy (Hubble type: %s)"), getType());
198 }
199 
200 
getForm() const201 GalacticForm* Galaxy::getForm() const
202 {
203     return form;
204 }
205 
getObjTypeName() const206 const char* Galaxy::getObjTypeName() const
207 {
208     return "galaxy";
209 }
210 
211 
212 // TODO: This value is just a guess.
213 // To be optimal, it should actually be computed:
214 static const float RADIUS_CORRECTION     = 0.025f;
215 static const float MAX_SPIRAL_THICKNESS  = 0.06f;
216 
pick(const Ray3d & ray,double & distanceToPicker,double & cosAngleToBoundCenter) const217 bool Galaxy::pick(const Ray3d& ray,
218                   double& distanceToPicker,
219                   double& cosAngleToBoundCenter) const
220 {
221     if (!isVisible())
222         return false;
223 
224     // The ellipsoid should be slightly larger to compensate for the fact
225     // that blobs are considered points when galaxies are built, but have size
226     // when they are drawn.
227     float yscale = (type < E0 )? MAX_SPIRAL_THICKNESS: form->scale.y + RADIUS_CORRECTION;
228     Vec3d ellipsoidAxes(getRadius()*(form->scale.x + RADIUS_CORRECTION),
229                         getRadius()* yscale,
230                         getRadius()*(form->scale.z + RADIUS_CORRECTION));
231 
232     Quatf qf= getOrientation();
233     Quatd qd(qf.w, qf.x, qf.y, qf.z);
234 
235     return testIntersection(Ray3d(Point3d() + (ray.origin - getPosition()), ray.direction) * conjugate(qd).toMatrix3(),
236                             Ellipsoidd(ellipsoidAxes),
237                             distanceToPicker,
238                             cosAngleToBoundCenter);
239 }
240 
241 
load(AssociativeArray * params,const string & resPath)242 bool Galaxy::load(AssociativeArray* params, const string& resPath)
243 {
244     double detail = 1.0;
245     params->getNumber("Detail", detail);
246     setDetail((float) detail);
247 
248 	string customTmpName;
249 	if(params->getString("CustomTemplate",customTmpName))
250 		setCustomTmpName(customTmpName);
251 
252     string typeName;
253     params->getString("Type", typeName);
254     setType(typeName);
255 
256     return DeepSkyObject::load(params, resPath);
257 }
258 
259 
render(const GLContext & context,const Vec3f & offset,const Quatf & viewerOrientation,float brightness,float pixelSize)260 void Galaxy::render(const GLContext& context,
261                     const Vec3f& offset,
262                     const Quatf& viewerOrientation,
263                     float brightness,
264                     float pixelSize)
265 {
266     if (form == NULL)
267         renderGalaxyEllipsoid(context, offset, viewerOrientation, brightness, pixelSize);
268     else
269         renderGalaxyPointSprites(context, offset, viewerOrientation, brightness, pixelSize);
270 }
271 
272 
renderGalaxyPointSprites(const GLContext &,const Vec3f & offset,const Quatf & viewerOrientation,float brightness,float pixelSize)273 void Galaxy::renderGalaxyPointSprites(const GLContext&,
274                                       const Vec3f& offset,
275                                       const Quatf& viewerOrientation,
276                                       float brightness,
277                                       float pixelSize)
278 {
279     if (form == NULL)
280         return;
281 
282     /* We'll first see if the galaxy's apparent size is big enough to
283        be noticeable on screen; if it's not we'll break right here,
284        avoiding all the overhead of the matrix transformations and
285        GL state changes: */
286         float distanceToDSO = offset.length() - getRadius();
287         if (distanceToDSO < 0)
288             distanceToDSO = 0;
289 
290         float minimumFeatureSize = pixelSize * distanceToDSO;
291         float size  = 2 * getRadius();
292 
293         if (size < minimumFeatureSize)
294             return;
295 
296     if (galaxyTex == NULL)
297     {
298         galaxyTex = CreateProceduralTexture(width, height, GL_RGBA,
299                                             GalaxyTextureEval);
300     }
301     assert(galaxyTex != NULL);
302 
303     glEnable(GL_TEXTURE_2D);
304     galaxyTex->bind();
305 
306     Mat3f viewMat = viewerOrientation.toMatrix3();
307     Vec3f v0 = Vec3f(-1, -1, 0) * viewMat;
308     Vec3f v1 = Vec3f( 1, -1, 0) * viewMat;
309     Vec3f v2 = Vec3f( 1,  1, 0) * viewMat;
310     Vec3f v3 = Vec3f(-1,  1, 0) * viewMat;
311 
312     //Mat4f m = (getOrientation().toMatrix4() *
313     //           Mat4f::scaling(form->scale) *
314     //           Mat4f::scaling(getRadius()));
315 
316     Mat3f m =
317         Mat3f::scaling(form->scale)*getOrientation().toMatrix3()*Mat3f::scaling(size);
318 
319     // Note: fixed missing factor of 2 in getRadius() scaling of galaxy diameter!
320     // Note: fixed correct ordering of (non-commuting) operations!
321 
322     int   pow2  = 1;
323 
324     vector<Blob>* points = form->blobs;
325     unsigned int nPoints = (unsigned int) (points->size() * clamp(getDetail()));
326     // corrections to avoid excessive brightening if viewed e.g. edge-on
327 
328     float brightness_corr = 1.0f;
329     float cosi;
330 
331     if (type < E0 || type > E3) //all galaxies, except ~round elliptics
332     {
333         cosi = Vec3f(0,1,0) * getOrientation().toMatrix3()
334                             * offset/offset.length();
335         brightness_corr = (float) sqrt(abs(cosi));
336         if (brightness_corr < 0.2f)
337             brightness_corr = 0.2f;
338     }
339     if (type > E3) // only elliptics with higher ellipticities
340     {
341         cosi = Vec3f(1,0,0) * getOrientation().toMatrix3()
342                             * offset/offset.length();
343         brightness_corr = brightness_corr * (float) abs((cosi));
344         if (brightness_corr < 0.45f)
345             brightness_corr = 0.45f;
346     }
347 
348     glBegin(GL_QUADS);
349     for (unsigned int i = 0; i < nPoints; ++i)
350     {
351         if ((i & pow2) != 0)
352         {
353             pow2 <<= 1;
354             size /= 1.55f;
355             if (size < minimumFeatureSize)
356                 break;
357         }
358 
359         Blob    b  = (*points)[i];
360         Point3f p  = b.position * m;
361         float   br = b.brightness / 255.0f;
362 
363         Color   c      = colorTable[b.colorIndex];     // lookup static color table
364         Point3f relPos = p + offset;
365 
366         float screenFrac = size / relPos.distanceFromOrigin();
367         if (screenFrac < 0.1f)
368         {
369             float btot = ((type > SBc) && (type < Irr))? 2.5f: 5.0f;
370             float a  = btot * (0.1f - screenFrac) * brightness_corr * brightness * br;
371 
372             glColor4f(c.red(), c.green(), c.blue(), (4.0f*lightGain + 1.0f)*a);
373 
374             glTexCoord2f(0, 0);          glVertex(p + (v0 * size));
375             glTexCoord2f(1, 0);          glVertex(p + (v1 * size));
376             glTexCoord2f(1, 1);          glVertex(p + (v2 * size));
377             glTexCoord2f(0, 1);          glVertex(p + (v3 * size));
378         }
379     }
380     glEnd();
381 }
382 
383 
renderGalaxyEllipsoid(const GLContext & context,const Vec3f & offset,const Quatf &,float,float pixelSize)384 void Galaxy::renderGalaxyEllipsoid(const GLContext& context,
385                                    const Vec3f& offset,
386                                    const Quatf&,
387                                    float,
388                                    float pixelSize)
389 {
390     float discSizeInPixels = pixelSize * getRadius() / offset.length();
391     unsigned int nRings = (unsigned int) (discSizeInPixels / 4.0f);
392     unsigned int nSlices = (unsigned int) (discSizeInPixels / 4.0f);
393     nRings = max(nRings, 100u);
394     nSlices = max(nSlices, 100u);
395 
396     VertexProcessor* vproc = context.getVertexProcessor();
397     if (vproc == NULL)
398         return;
399 
400     //int e = min(max((int) type - (int) E0, 0), 7);
401     Vec3f scale = Vec3f(1.0f, 0.9f, 1.0f) * getRadius();
402     Vec3f eyePos_obj = -offset * (~getOrientation()).toMatrix3();
403 
404     vproc->enable();
405     vproc->use(vp::ellipticalGalaxy);
406 
407     vproc->parameter(vp::EyePosition, eyePos_obj);
408     vproc->parameter(vp::Scale, scale);
409     vproc->parameter(vp::InverseScale,
410                      Vec3f(1.0f / scale.x, 1.0f / scale.y, 1.0f / scale.z));
411     vproc->parameter((vp::Parameter) 23, eyePos_obj.length() / scale.x, 0.0f, 0.0f, 0.0f);
412 
413     glRotate(getOrientation());
414 
415     glDisable(GL_TEXTURE_2D);
416     glColor4f(1.0f, 1.0f, 1.0f, 0.3f);
417     for (unsigned int i = 0; i < nRings; i++)
418     {
419         float phi0 = (float) PI * ((float) i / (float) nRings - 0.5f);
420         float phi1 = (float) PI * ((float) (i + 1) / (float) nRings - 0.5f);
421 
422         glBegin(GL_QUAD_STRIP);
423         for (unsigned int j = 0; j <= nSlices; j++)
424         {
425             float theta = (float) (PI * 2) * (float) j / (float) nSlices;
426             float sinTheta = (float) sin(theta);
427             float cosTheta = (float) cos(theta);
428 
429             glVertex3f((float) cos(phi0) * cosTheta * scale.x,
430                        (float) sin(phi0)            * scale.y,
431                        (float) cos(phi0) * sinTheta * scale.z);
432             glVertex3f((float) cos(phi1) * cosTheta * scale.x,
433                        (float) sin(phi1)            * scale.y,
434                        (float) cos(phi1) * sinTheta * scale.z);
435         }
436         glEnd();
437     }
438     glEnable(GL_TEXTURE_2D);
439 
440     vproc->disable();
441 }
442 
443 
getRenderMask() const444 unsigned int Galaxy::getRenderMask() const
445 {
446     return Renderer::ShowGalaxies;
447 }
448 
449 
getLabelMask() const450 unsigned int Galaxy::getLabelMask() const
451 {
452     return Renderer::GalaxyLabels;
453 }
454 
455 
increaseLightGain()456 void Galaxy::increaseLightGain()
457 {
458     lightGain  += 0.05f;
459     if (lightGain > 1.0f)
460         lightGain  = 1.0f;
461 }
462 
463 
decreaseLightGain()464 void Galaxy::decreaseLightGain()
465 {
466     lightGain  -= 0.05f;
467     if (lightGain < 0.0f)
468         lightGain  = 0.0f;
469 }
470 
471 
getLightGain()472 float Galaxy::getLightGain()
473 {
474     return lightGain;
475 }
476 
477 
setLightGain(float lg)478 void Galaxy::setLightGain(float lg)
479 {
480     if (lg < 0.0f)
481         lightGain = 0.0f;
482     else if (lg > 1.0f)
483         lightGain = 1.0f;
484     else
485         lightGain = lg;
486 }
487 
488 
buildGalacticForms(const std::string & filename)489 GalacticForm* buildGalacticForms(const std::string& filename)
490 {
491 	Blob b;
492 	vector<Blob>* galacticPoints = new vector<Blob>;
493 
494 	// Load templates in standard .png format
495 	int width, height, rgb, j = 0, kmin = 9;
496 	unsigned char value;
497 	float h = 0.75f;
498 	Image* img;
499 	img = LoadPNGImage(filename);
500 	if (img == NULL)
501 	{
502 	cout<<"\nThe galaxy template *** "<<filename<<" *** could not be loaded!\n\n";
503 	return NULL;
504 	}
505 	width  = img->getWidth();
506 	height = img->getHeight();
507 	rgb    = img->getComponents();
508 
509 		for (int i = 0; i < width * height; i++)
510 		{
511 			value = img->getPixels()[rgb * i];
512 			if (value > 10)
513 			{
514 				float x, y, z, r2, yy, prob;
515 				z  = floor(i /(float) width);
516 				x  = (i - width * z - 0.5f * (width - 1)) / (float) width;
517 				z  = (0.5f * (height - 1) - z) / (float) height;
518 				x  += Mathf::sfrand() * 0.008f;
519 				z  += Mathf::sfrand() * 0.008f;
520 				r2 = x * x + z * z;
521 
522 				if ( strcmp ( filename.c_str(), "models/E0.png") != 0 )
523 				{
524 					float y0 = 0.5f * MAX_SPIRAL_THICKNESS * sqrt((float)value/256.0f) * exp(- 5.0f * r2);
525 					float B, yr;
526 					B = (r2 > 0.35f)? 1.0f: 0.75f; // the darkness of the "dust lane", 0 < B < 1
527 					float p0 = 1.0f - B * exp(-h * h); // the uniform reference probability, envelopping prob*p0.
528 					do
529 					{
530 						// generate "thickness" y of spirals with emulation of a dust lane
531 						// in galctic plane (y=0)
532 
533 						yr =  Mathf::sfrand() * h;
534 						prob = (1.0f - B * exp(-yr * yr))/p0;
535 
536 					} while (Mathf::frand() > prob);
537 					b.brightness  = value * prob;
538 					y = y0 * yr / h;
539 				}
540 				else
541 				{
542 					// generate spherically symmetric distribution from E0.png
543 					do
544 					{
545 						yy = Mathf::sfrand();
546 						float ry2 = 1.0f - yy * yy;
547 						prob = ry2 > 0? sqrt(ry2): 0.0f;
548 					} while (Mathf::frand() > prob);
549 					y = yy * sqrt(0.25f - r2) ;
550 					b.brightness  = value;
551 					kmin = 12;
552 				}
553 
554 				b.position    = Point3f(x, y, z);
555 				unsigned int rr =  (unsigned int) (b.position.distanceFromOrigin() * 511);
556 				b.colorIndex  = rr < 256? rr: 255;
557 				galacticPoints->push_back(b);
558 				j++;
559 			 }
560 		}
561 
562     delete img;
563 	galacticPoints->reserve(j);
564 
565 	// sort to start with the galaxy center region (x^2 + y^2 + z^2 ~ 0), such that
566 	// the biggest (brightest) sprites will be localized there!
567 
568 	sort(galacticPoints->begin(), galacticPoints->end());
569 
570 	// reshuffle the galaxy points randomly...except the first kmin+1 in the center!
571 	// the higher that number the stronger the central "glow"
572 
573 	random_shuffle( galacticPoints->begin() + kmin, galacticPoints->end());
574 
575 	GalacticForm* galacticForm  = new GalacticForm();
576 	galacticForm->blobs         = galacticPoints;
577 	galacticForm->scale         = Vec3f(1.0f, 1.0f, 1.0f);
578 
579 	return galacticForm;
580 }
581 
582 
InitializeForms()583 void InitializeForms()
584 {
585     // build color table:
586 
587     for (unsigned int i = 0; i < 256; i++)
588     {
589         float rr, gg, bb;
590         //
591         // generic Hue profile as deduced from true-color imaging for spirals
592         // Hue in degrees
593 
594         float hue = (i < 28)? 25 * tanh(0.0615f * (27 - i)): 25 * tanh(0.0615f * (27 - i)) + 220;
595 
596         //convert Hue to RGB
597 
598 		DeepSkyObject::hsv2rgb(&rr, &gg, &bb, hue, 0.20f, 1.0f);
599         colorTable[i]  = Color(rr, gg, bb);
600     }
601     // Spiral Galaxies, 7 classical Hubble types
602 
603     spiralForms   = new GalacticForm*[7];
604 
605     spiralForms[Galaxy::S0]   = buildGalacticForms("models/S0.png");
606     spiralForms[Galaxy::Sa]   = buildGalacticForms("models/Sa.png");
607     spiralForms[Galaxy::Sb]   = buildGalacticForms("models/Sb.png");
608     spiralForms[Galaxy::Sc]   = buildGalacticForms("models/Sc.png");
609     spiralForms[Galaxy::SBa]  = buildGalacticForms("models/SBa.png");
610     spiralForms[Galaxy::SBb]  = buildGalacticForms("models/SBb.png");
611     spiralForms[Galaxy::SBc]  = buildGalacticForms("models/SBc.png");
612 
613     // Elliptical Galaxies , 8 classical Hubble types, E0..E7,
614     //
615     // To save space: generate spherical E0 template from S0 disk
616     // via rescaling by (1.0f, 3.8f, 1.0f).
617 
618     ellipticalForms = new GalacticForm*[8];
619     for (unsigned int eform  = 0; eform <= 7; ++eform)
620     {
621         float ell = 1.0f - (float) eform / 8.0f;
622 
623         // note the correct x,y-alignment of 'ell' scaling!!
624    		// build all elliptical templates from rescaling E0
625 
626    		ellipticalForms[eform] = buildGalacticForms("models/E0.png");
627    		if (*ellipticalForms)
628    			ellipticalForms[eform]->scale = Vec3f(ell, ell, 1.0f);
629 
630         // account for reddening of ellipticals rel.to spirals
631         if (*ellipticalForms)
632         {
633         	unsigned int nPoints = (unsigned int) (ellipticalForms[eform]->blobs->size());
634 			for (unsigned int i = 0; i < nPoints; ++i)
635     		{
636             	(*ellipticalForms[eform]->blobs)[i].colorIndex = (unsigned int) ceil(0.76f * (*ellipticalForms[eform]->blobs)[i].colorIndex);
637         	}
638         }
639     }
640     //Irregular Galaxies
641     unsigned int galaxySize = GALAXY_POINTS, ip = 0;
642     Blob b;
643     Point3f p;
644 
645     vector<Blob>* irregularPoints = new vector<Blob>;
646     irregularPoints->reserve(galaxySize);
647 
648     while (ip < galaxySize)
649     {
650         p        = Point3f(Mathf::sfrand(), Mathf::sfrand(), Mathf::sfrand());
651         float r  = p.distanceFromOrigin();
652         if (r < 1)
653         {
654             float prob = (1 - r) * (fractalsum(Point3f(p.x + 5, p.y + 5, p.z + 5), 8) + 1) * 0.5f;
655             if (Mathf::frand() < prob)
656             {
657                 b.position   = p;
658                 b.brightness = 64u;
659                 unsigned int rr =  (unsigned int) (r * 511);
660         	    b.colorIndex  = rr < 256? rr: 255;
661                 irregularPoints->push_back(b);
662                 ++ip;
663             }
664         }
665     }
666     irregularForm        = new GalacticForm();
667     irregularForm->blobs = irregularPoints;
668     irregularForm->scale = Vec3f(0.5f, 0.5f, 0.5f);
669 
670     formsInitialized = true;
671 }
672 
673 
operator <<(ostream & s,const Galaxy::GalaxyType & sc)674 ostream& operator<<(ostream& s, const Galaxy::GalaxyType& sc)
675 {
676     return s << GalaxyTypeNames[static_cast<int>(sc)].name;
677 }
678