1 /*
2  * Stellarium
3  * Copyright (C) 2010 Bogdan Marinov
4  * Copyright (C) 2014 Georg Zotti (Tails)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19  */
20 
21 #include "Comet.hpp"
22 #include "Orbit.hpp"
23 
24 #include "StelApp.hpp"
25 #include "StelCore.hpp"
26 #include "StelPainter.hpp"
27 #include "StelObserver.hpp"
28 
29 #include "StelTexture.hpp"
30 #include "StelTextureMgr.hpp"
31 #include "StelToneReproducer.hpp"
32 #include "StelTranslator.hpp"
33 #include "StelUtils.hpp"
34 #include "StelFileMgr.hpp"
35 #include "StelMovementMgr.hpp"
36 #include "StelModuleMgr.hpp"
37 #include "LandscapeMgr.hpp"
38 
39 #include <QDebug>
40 #include <QElapsedTimer>
41 
42 // for compute tail shape
43 #define COMET_TAIL_SLICES 16u // segments around the perimeter
44 #define COMET_TAIL_STACKS 16u // cuts along the rotational axis
45 
46 // These are to avoid having index arrays for each comet when all are equal.
47 bool Comet::createTailIndices=true;
48 bool Comet::createTailTextureCoords=true;
49 StelTextureSP Comet::comaTexture;
50 StelTextureSP Comet::tailTexture;
51 QVector<Vec2f> Comet::tailTexCoordArr; // computed only once for all Comets.
52 QVector<unsigned short> Comet::tailIndices; // computed only once for all Comets.
53 
Comet(const QString & englishName,double radius,double oblateness,Vec3f halocolor,float albedo,float roughness,float outgas_intensity,float outgas_falloff,const QString & atexMapName,const QString & aobjModelName,posFuncType coordFunc,KeplerOrbit * orbitPtr,OsculatingFunctType * osculatingFunc,bool acloseOrbit,bool hidden,const QString & pTypeStr,float dustTailWidthFact,float dustTailLengthFact,float dustTailBrightnessFact)54 Comet::Comet(const QString& englishName,
55 	     double radius,
56 	     double oblateness,
57 	     Vec3f halocolor,
58 	     float albedo,
59 	     float roughness,
60 	     float outgas_intensity,
61 	     float outgas_falloff,
62 	     const QString& atexMapName,
63 	     const QString& aobjModelName,
64 	     posFuncType coordFunc,
65 	     KeplerOrbit* orbitPtr,
66 	     OsculatingFunctType *osculatingFunc,
67 	     bool acloseOrbit,
68 	     bool hidden,
69 	     const QString& pTypeStr,
70 	     float dustTailWidthFact,
71 	     float dustTailLengthFact,
72 	     float dustTailBrightnessFact)
73 	: Planet (englishName,
74 		  radius,
75 		  oblateness,
76 		  halocolor,
77 		  albedo,
78 		  roughness,
79 		  atexMapName,
80 		  "", // no normalmap.
81 		  aobjModelName,
82 		  coordFunc,
83 		  orbitPtr,
84 		  osculatingFunc,
85 		  acloseOrbit,
86 		  hidden,
87 		  false, //No atmosphere
88 		  true, //halo
89 		  pTypeStr),
90 	  slopeParameter(-10.f), // -10 == uninitialized: used in getVMagnitude()
91 	  isCometFragment(false),
92 	  nameIsProvisionalDesignation(false),
93 	  tailFactors(-1., -1.), // mark "invalid"
94 	  tailActive(false),
95 	  tailBright(false),
96 	  deltaJDEtail(15.0*StelCore::JD_MINUTE), // update tail geometry every 15 minutes only
97 	  lastJDEtail(0.0),
98 	  dustTailWidthFactor(dustTailWidthFact),
99 	  dustTailLengthFactor(dustTailLengthFact),
100 	  dustTailBrightnessFactor(dustTailBrightnessFact),
101 	  intensityFovScale(1.0f),
102 	  intensityMinFov(0.001f), // when zooming in further, Coma is no longer visible.
103 	  intensityMaxFov(0.010f) // when zooming out further, MilkyComa is fully visible (when enabled).
104 {
105 	this->outgas_intensity =outgas_intensity;
106 	this->outgas_falloff   =outgas_falloff;
107 
108 	gastailVertexArr.clear();
109 	dusttailVertexArr.clear();
110 	comaVertexArr.clear();
111 	gastailColorArr.clear();
112 	dusttailColorArr.clear();
113 
114 	//TODO: Name processing?
115 }
116 
~Comet()117 Comet::~Comet()
118 {
119 }
120 
setAbsoluteMagnitudeAndSlope(const float magnitude,const float slope)121 void Comet::setAbsoluteMagnitudeAndSlope(const float magnitude, const float slope)
122 {
123 	if ((slope < -2.5f) || (slope > 25.0f))
124 	{
125 		// Slope G can become slightly smaller than 0. -10 is mark of invalidity.
126 		qDebug() << "Warning: Suspect slope parameter value" << slope << "for comet" << englishName << "(rarely exceeding -1...20)";
127 		return;
128 	}
129 	absoluteMagnitude = magnitude;
130 	slopeParameter = slope;
131 }
132 
translateName(const StelTranslator & translator)133 void Comet::translateName(const StelTranslator &translator)
134 {
135 	nameI18 = translator.qtranslate(englishName, "comet");
136 }
137 
getInfoStringAbsoluteMagnitude(const StelCore * core,const InfoStringGroup & flags) const138 QString Comet::getInfoStringAbsoluteMagnitude(const StelCore *core, const InfoStringGroup& flags) const
139 {
140 	Q_UNUSED(core)
141 	QString str;
142 	QTextStream oss(&str);
143 	if (flags&AbsoluteMagnitude)
144 	{
145 		//TODO: Make sure absolute magnitude is a sane value
146 		//If the two parameter magnitude system is not used, don't display this
147 		//value. (Using radius/albedo doesn't make any sense for comets.)
148 		// Note that slope parameter can be <0 (down to -2?), so -10 is now used for "uninitialized"
149 		if (slopeParameter >= -9.9f)
150 			oss << QString("%1: %2<br/>").arg(q_("Absolute Magnitude")).arg(absoluteMagnitude, 0, 'f', 2);
151 	}
152 
153 	return str;
154 }
155 
getInfoStringSize(const StelCore * core,const InfoStringGroup & flags) const156 QString Comet::getInfoStringSize(const StelCore *core, const InfoStringGroup &flags) const
157 {
158 	// TRANSLATORS: Unit of measure for distance - kilometers
159 	QString km = qc_("km", "distance");
160 	// TRANSLATORS: Unit of measure for distance - milliones kilometers
161 	QString Mkm = qc_("M km", "distance");
162 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
163 	QString str;
164 	QTextStream oss(&str);
165 
166 	if (flags&Size)
167 	{
168 		// Given the very irregular shape, other terminology like "equatorial radius" do not make much sense.
169 		oss << QString("%1: %2 %3<br/>").arg(q_("Core diameter"), QString::number(AU * 2.0 * getEquatorialRadius(), 'f', 1) , qc_("km", "distance"));
170 	}
171 	if ((flags&Size) && (tailFactors[0]>0.0f))
172 	{
173 		// GZ: Add estimates for coma diameter and tail length.
174 		QString comaEst = q_("Coma diameter (estimate)");
175 		const double coma = floor(static_cast<double>(tailFactors[0])*AU/1000.0)*1000.0;
176 		const double tail = static_cast<double>(tailFactors[1])*AU;
177 		const double distanceKm = AU * getJ2000EquatorialPos(core).length();
178 		// Try to estimate tail length in degrees.
179 		// TODO: Take projection effect into account!
180 		// The estimates here assume that the tail is seen from the side.
181 		QString comaDeg, tailDeg;
182 		if (withDecimalDegree)
183 		{
184 			comaDeg = StelUtils::radToDecDegStr(atan(coma/distanceKm),4,false,true);
185 			tailDeg = StelUtils::radToDecDegStr(atan(tail/distanceKm),4,false,true);
186 		}
187 		else
188 		{
189 			comaDeg = StelUtils::radToDmsStr(atan(coma/distanceKm));
190 			tailDeg = StelUtils::radToDmsStr(atan(tail/distanceKm));
191 		}
192 		if (coma>1e6)
193 			oss << QString("%1: %2 %3 (%4)<br/>").arg(comaEst, QString::number(coma*1e-6, 'G', 3), Mkm, comaDeg);
194 		else
195 			oss << QString("%1: %2 %3 (%4)<br/>").arg(comaEst, QString::number(coma, 'f', 0), km, comaDeg);
196 		oss << QString("%1: %2 %3 (%4)<br/>").arg(q_("Gas tail length (estimate)"), QString::number(tail*1e-6, 'G', 3), Mkm, tailDeg);
197 	}
198 	return str;
199 }
200 
201 // Nothing interesting?
getInfoStringExtra(const StelCore * core,const InfoStringGroup & flags) const202 QString Comet::getInfoStringExtra(const StelCore *core, const InfoStringGroup &flags) const
203 {
204 	Q_UNUSED(core) Q_UNUSED(flags)
205 
206 	return QString();
207 }
208 
getInfoMap(const StelCore * core) const209 QVariantMap Comet::getInfoMap(const StelCore *core) const
210 {
211 	QVariantMap map = Planet::getInfoMap(core);
212 	map.insert("tail-length-km", tailFactors[1]*AUf);
213 	map.insert("coma-diameter-km", tailFactors[0]*AUf);
214 
215 	return map;
216 }
217 
getSiderealPeriod() const218 double Comet::getSiderealPeriod() const
219 {
220 	const double semiMajorAxis=static_cast<KeplerOrbit*>(orbitPtr)->getSemimajorAxis();
221 	return ((semiMajorAxis>0) ? KeplerOrbit::calculateSiderealPeriod(semiMajorAxis, 1.0) : 0.);
222 }
223 
getVMagnitude(const StelCore * core) const224 float Comet::getVMagnitude(const StelCore* core) const
225 {
226 	//If the two parameter system is not used,
227 	//use the default radius/albedo mechanism
228 	if (slopeParameter < -9.0f)
229 	{
230 		return Planet::getVMagnitude(core);
231 	}
232 
233 	//Calculate distances
234 	const Vec3d& observerHeliocentricPosition = core->getObserverHeliocentricEclipticPos();
235 	const Vec3d& cometHeliocentricPosition = getHeliocentricEclipticPos();
236 	const float cometSunDistance = static_cast<float>(cometHeliocentricPosition.length());
237 	const float observerCometDistance = static_cast<float>((observerHeliocentricPosition - cometHeliocentricPosition).length());
238 
239 	//Calculate apparent magnitude
240 	//Sources: http://www.clearskyinstitute.com/xephem/help/xephem.html#mozTocId564354
241 	//(XEphem manual, section 7.1.2.3 "Magnitude models"), also
242 	//http://www.ayton.id.au/gary/Science/Astronomy/Ast_comets.htm#Comet%20facts:
243 	// GZ: Note that Meeus, Astr.Alg.1998 p.231, has m=absoluteMagnitude+5log10(observerCometDistance) + kappa*log10(cometSunDistance)
244 	// with kappa typically 5..15. MPC provides Slope parameter. So we should expect to have slopeParameter (a word only used for minor planets!) for our comets 2..6
245 	return absoluteMagnitude + 5.f * std::log10(observerCometDistance) + 2.5f * slopeParameter * std::log10(cometSunDistance);
246 }
247 
update(int deltaTime)248 void Comet::update(int deltaTime)
249 {
250 	Planet::update(deltaTime);
251 
252 	//calculate FOV fade value, linear fade between intensityMaxFov and intensityMinFov
253 	const float vfov = static_cast<float>(StelApp::getInstance().getCore()->getMovementMgr()->getCurrentFov());
254 	intensityFovScale = qBound(0.25f,(vfov - intensityMinFov) / (intensityMaxFov - intensityMinFov),1.0f);
255 
256 	// The rest deals with updating tail geometries and brightness
257 	StelCore* core=StelApp::getInstance().getCore();
258 	double dateJDE=core->getJDE();
259 
260 	if (!static_cast<KeplerOrbit*>(orbitPtr)->objectDateValid(dateJDE)) return; // don't do anything if out of useful date range. This allows having hundreds of comet elements.
261 
262 	//GZ: I think we can make deltaJDtail adaptive, depending on distance to sun! For some reason though, this leads to a crash!
263 	//deltaJDtail=StelCore::JD_SECOND * qBound(1.0, eclipticPos.length(), 20.0);
264 
265 	if (fabs(lastJDEtail-dateJDE)>deltaJDEtail)
266 	{
267 		lastJDEtail=dateJDE;
268 
269 		if (static_cast<KeplerOrbit*>(orbitPtr)->getUpdateTails()){
270 			// Compute lengths and orientations from orbit object, but only if required.
271 			tailFactors=getComaDiameterAndTailLengthAU();
272 
273 			// Note that we use a diameter larger than what the formula returns. A scale factor of 1.2 is ad-hoc/empirical (GZ), but may look better.
274 			computeComa(1.0f*tailFactors[0]); // TBD: APPARENTLY NO SCALING? REMOVE 1.0 and note above.
275 
276 			tailActive = (tailFactors[1] > tailFactors[0]); // Inhibit tails drawing if too short. Would be nice to include geometric projection angle, but this is too costly.
277 
278 			if (tailActive)
279 			{
280 				float gasTailEndRadius=qMax(tailFactors[0], 0.025f*tailFactors[1]) ; // This avoids too slim gas tails for bright comets like Hale-Bopp.
281 				float gasparameter=gasTailEndRadius*gasTailEndRadius/(2.0f*tailFactors[1]); // parabola formula: z=r²/2p, so p=r²/2z
282 				// The dust tail is thicker and usually shorter. The factors can be configured in the elements.
283 				float dustparameter=gasTailEndRadius*gasTailEndRadius*dustTailWidthFactor*dustTailWidthFactor/(2.0f*dustTailLengthFactor*tailFactors[1]);
284 
285 				// Find valid parameters to create paraboloid vertex arrays: dustTail, gasTail.
286 				computeParabola(gasparameter, gasTailEndRadius, -0.5f*gasparameter, gastailVertexArr,  tailTexCoordArr, tailIndices);
287 				//gastailColorArr.fill(Vec3f(0.3,0.3,0.3), gastailVertexArr.length());
288 				// Now we make a skewed parabola. Skew factor (xOffset, last arg) is rather ad-hoc/empirical. TBD later: Find physically correct solution.
289 				computeParabola(dustparameter, dustTailWidthFactor*gasTailEndRadius, -0.5f*dustparameter, dusttailVertexArr, tailTexCoordArr, tailIndices, 25.0f*static_cast<float>(static_cast<KeplerOrbit*>(orbitPtr)->getVelocity().length()));
290 				//dusttailColorArr.fill(Vec3f(0.3,0.3,0.3), dusttailVertexArr.length());
291 
292 
293 				// 2014-08 for 0.13.1 Moved from drawTail() to save lots of computation per frame (There *are* folks downloading all 730 MPC current comet elements...)
294 				// Find rotation matrix from 0/0/1 to eclipticPosition: crossproduct for axis (normal vector), dotproduct for angle.
295 				Vec3d eclposNrm=eclipticPos+aberrationPush; eclposNrm.normalize();
296 				gasTailRot=Mat4d::rotation(Vec3d(0.0, 0.0, 1.0)^(eclposNrm), std::acos(Vec3d(0.0, 0.0, 1.0).dot(eclposNrm)) );
297 
298 				Vec3d velocity=static_cast<KeplerOrbit*>(orbitPtr)->getVelocity(); // [AU/d]
299 				// This was a try to rotate a straight parabola somewhat away from the antisolar direction.
300 				//Mat4d dustTailRot=Mat4d::rotation(eclposNrm^(-velocity), 0.15f*std::acos(eclposNrm.dot(-velocity))); // GZ: This scale factor of 0.15 is empirical from photos of Halley and Hale-Bopp.
301 				// The curved tail is curved towards positive X. We first rotate around the Z axis into a direction opposite of the motion vector, then again the antisolar rotation applies.
302 				// In addition, we let the dust tail already start with a light tilt.
303 				dustTailRot=gasTailRot * Mat4d::zrotation(atan2(velocity[1], velocity[0]) + M_PI) * Mat4d::yrotation(5.0*velocity.length());
304 
305 				// Rotate vertex arrays:
306 				Vec3d* gasVertices= static_cast<Vec3d*>(gastailVertexArr.data());
307 				Vec3d* dustVertices=static_cast<Vec3d*>(dusttailVertexArr.data());
308 				for (unsigned short int i=0; i<COMET_TAIL_SLICES*COMET_TAIL_STACKS+1; ++i)
309 				{
310 					gasVertices[i].transfo4d(gasTailRot);
311 					dustVertices[i].transfo4d(dustTailRot);
312 				}
313 			}
314 			static_cast<KeplerOrbit*>(orbitPtr)->setUpdateTails(false); // don't update until position has been recalculated elsewhere
315 		}
316 	}
317 
318 	// And also update magnitude and tail brightness/extinction here.
319 	const bool withAtmosphere=(core->getSkyDrawer()->getFlagHasAtmosphere());
320 
321 	StelToneReproducer* eye = core->getToneReproducer();
322 	const float lum = core->getSkyDrawer()->surfaceBrightnessToLuminance(getVMagnitude(core)+13.0f); // How to calibrate?
323 	// Get the luminance scaled between 0 and 1
324 	float aLum =eye->adaptLuminanceScaled(lum);
325 
326 
327 	// To make comet more apparent in overviews, take field of view into account:
328 	const float fov=core->getProjection(core->getAltAzModelViewTransform())->getFov();
329 	if (fov>20)
330 		aLum*= (fov/20.0f);
331 
332 	// Now inhibit tail drawing if still too dim.
333 	if (aLum<0.002f)
334 	{
335 		// Far too dim: don't even show tail...
336 		tailBright=false;
337 		return;
338 	} else
339 		tailBright=true;
340 
341 	// Separate factors, but avoid overly bright tails. I limit to about 0.7 for overlapping both tails which should not exceed full-white.
342 	float gasMagFactor=qMin(0.9f*aLum, 0.7f);
343 	float dustMagFactor=qMin(dustTailBrightnessFactor*aLum, 0.7f);
344 
345 	// TODO: Maybe make gas color distance dependent? (various typical ingredients outgas at different temperatures...)
346 	Vec3f gasColor(0.15f*gasMagFactor,0.35f*gasMagFactor,0.6f*gasMagFactor); // Orig color 0.15/0.15/0.6.
347 	Vec3f dustColor(dustMagFactor, dustMagFactor,0.6f*dustMagFactor);
348 
349 	if (withAtmosphere)
350 	{
351 		Extinction extinction=core->getSkyDrawer()->getExtinction();
352 
353 		// Not only correct the color values for extinction, but for twilight conditions, also make tail end less visible.
354 		// I consider sky brightness over 1cd/m^2 as reason to shorten tail.
355 		// Below this brightness, the tail brightness loss by this method is insignificant:
356 		// Just counting through the vertices might make a spiral apperance. Maybe even better than stackwise? Let's see...
357 		const float avgAtmLum=GETSTELMODULE(LandscapeMgr)->getAtmosphereAverageLuminance();
358 		const float brightnessDecreasePerVertexFromHead=1.0f/(COMET_TAIL_SLICES*COMET_TAIL_STACKS)  * avgAtmLum;
359 		float brightnessPerVertexFromHead=1.0f;
360 
361 		gastailColorArr.clear();
362 		dusttailColorArr.clear();
363 		for (int i=0; i<gastailVertexArr.size(); ++i)
364 		{
365 			// Gastail extinction:
366 			Vec3d vertAltAz=core->j2000ToAltAz(gastailVertexArr.at(i), StelCore::RefractionOn);
367 			vertAltAz.normalize();
368 			Q_ASSERT(fabs(vertAltAz.lengthSquared()-1.0) < 0.001);
369 			float oneMag=0.0f;
370 			extinction.forward(vertAltAz, &oneMag);
371 			float extinctionFactor=std::pow(0.4f, oneMag); // drop of one magnitude: factor 2.5 or 40%
372 			gastailColorArr.append(gasColor*extinctionFactor* brightnessPerVertexFromHead*intensityFovScale);
373 
374 			// dusttail extinction:
375 			vertAltAz=core->j2000ToAltAz(dusttailVertexArr.at(i), StelCore::RefractionOn);
376 			vertAltAz.normalize();
377 			Q_ASSERT(fabs(vertAltAz.lengthSquared()-1.0) < 0.001);
378 			oneMag=0.0f;
379 			extinction.forward(vertAltAz, &oneMag);
380 			extinctionFactor=std::pow(0.4f, oneMag); // drop of one magnitude: factor 2.5 or 40%
381 			dusttailColorArr.append(dustColor*extinctionFactor * brightnessPerVertexFromHead*intensityFovScale);
382 
383 			brightnessPerVertexFromHead-=brightnessDecreasePerVertexFromHead;
384 		}
385 	}
386 	else // no atmosphere: set all vertices to same brightness.
387 	{
388 		gastailColorArr.fill(gasColor  *intensityFovScale, gastailVertexArr.length());
389 		dusttailColorArr.fill(dustColor*intensityFovScale, dusttailVertexArr.length());
390 	}
391 	//qDebug() << "Comet " << getEnglishName() <<  "JDE: " << date << "gasR" << gasColor[0] << " dustR" << dustColor[0];
392 }
393 
394 
395 // Draw the Comet and all the related infos: name, circle etc... GZ: Taken from Planet.cpp 2013-11-05 and extended
draw(StelCore * core,float maxMagLabels,const QFont & planetNameFont)396 void Comet::draw(StelCore* core, float maxMagLabels, const QFont& planetNameFont)
397 {
398 	if (hidden)
399 		return;
400 
401 	// Exclude drawing if user set a hard limit magnitude.
402 	if (core->getSkyDrawer()->getFlagPlanetMagnitudeLimit() && (getVMagnitude(core) > core->getSkyDrawer()->getCustomPlanetMagnitudeLimit()))
403 		return;
404 
405 	if (getEnglishName() == core->getCurrentLocation().planetName)
406 	{ // Maybe even don't do that? E.g., draw tail while riding the comet? Decide later.
407 		return;
408 	}
409 
410 	// This test seemed necessary for reasonable fps in case too many comet elements are loaded.
411 	// Problematic: Early-out here of course disables the wanted hint circles for dim comets.
412 	// The line makes hints for comets 5 magnitudes below sky limiting magnitude visible.
413 	// If comet is too faint to be seen, don't bother rendering. (Massive speedup if people have hundreds of comet elements!)
414 	if ((getVMagnitude(core)-5.0f) > core->getSkyDrawer()->getLimitMagnitude() && !core->getCurrentLocation().planetName.contains("Observer", Qt::CaseInsensitive))
415 	{
416 		return;
417 	}
418 	if (!static_cast<KeplerOrbit*>(orbitPtr)->objectDateValid(core->getJDE())) return; // don't draw at all if out of useful date range. This allows having hundreds of comet elements.
419 
420 	Mat4d mat = Mat4d::translation(eclipticPos+aberrationPush) * rotLocalToParent;
421 	// This removed totally the Planet shaking bug!!!
422 	StelProjector::ModelViewTranformP transfo = core->getHeliocentricEclipticModelViewTransform();
423 	transfo->combine(mat);
424 
425 	// Compute the 2D position and check if in the screen
426 	const StelProjectorP prj = core->getProjection(transfo);
427 	const double screenSz = (getAngularSize(core))*M_PI_180*static_cast<double>(prj->getPixelPerRadAtCenter());
428 	const double viewport_left = prj->getViewportPosX();
429 	const double viewport_bottom = prj->getViewportPosY();
430 	const bool projectionValid=prj->project(Vec3d(0.), screenPos);
431 	if (projectionValid
432 		&& screenPos[1] > viewport_bottom - screenSz
433 		&& screenPos[1] < viewport_bottom + prj->getViewportHeight()+screenSz
434 		&& screenPos[0] > viewport_left - screenSz
435 		&& screenPos[0] < viewport_left + prj->getViewportWidth() + screenSz)
436 	{
437 		// Draw the name, and the circle if it's not too close from the body it's turning around
438 		// this prevents name overlapping (ie for jupiter satellites)
439 		float ang_dist = 300.f*static_cast<float>(atan(getEclipticPos().length()/getEquinoxEquatorialPos(core).length())/core->getMovementMgr()->getCurrentFov());
440 		// if (ang_dist==0.f) ang_dist = 1.f; // if ang_dist == 0, the Planet is sun.. --> GZ: we can remove it.
441 
442 		// by putting here, only draw orbit if Comet is visible for clarity
443 		drawOrbit(core);  // TODO - fade in here also...
444 
445 		labelsFader = (flagLabels && ang_dist>0.25f && maxMagLabels>getVMagnitude(core));
446 		drawHints(core, planetNameFont);
447 
448 		draw3dModel(core,transfo,static_cast<float>(screenSz));
449 	}
450 	else
451 		if (!projectionValid && prj.data()->getNameI18() == q_("Orthographic"))
452 			return; // End prematurely. This excludes bad "ghost" comet tail on the wrong hemisphere in ortho projection! Maybe also Fisheye, but it's less problematic.
453 
454 	// If comet is too faint to be seen, don't bother rendering. (Massive speedup if people have hundreds of comets!)
455 	// This test moved here so that hints are still drawn.
456 	if ((getVMagnitude(core)-3.0f) > core->getSkyDrawer()->getLimitMagnitude())
457 	{
458 		return;
459 	}
460 
461 	// but tails should also be drawn if comet core is off-screen...
462 	if (tailActive && tailBright)
463 	{
464 		drawTail(core,transfo,true);  // gas tail
465 		drawTail(core,transfo,false); // dust tail
466 	}
467 	//Coma: this is just a fan disk tilted towards the observer;-)
468 	drawComa(core, transfo);
469 	return;
470 }
471 
drawTail(StelCore * core,StelProjector::ModelViewTranformP transfo,bool gas)472 void Comet::drawTail(StelCore* core, StelProjector::ModelViewTranformP transfo, bool gas)
473 {
474 	StelPainter sPainter(core->getProjection(transfo));
475 	sPainter.setBlending(true, GL_ONE, GL_ONE);
476 
477 	tailTexture->bind();
478 
479 	if (gas) {
480 		StelVertexArray vaGas(static_cast<const QVector<Vec3d> >(gastailVertexArr), StelVertexArray::Triangles,
481 				      static_cast<const QVector<Vec2f> >(tailTexCoordArr), tailIndices, static_cast<const QVector<Vec3f> >(gastailColorArr));
482 		sPainter.drawStelVertexArray(vaGas, true);
483 
484 	} else {
485 		StelVertexArray vaDust(static_cast<const QVector<Vec3d> >(dusttailVertexArr), StelVertexArray::Triangles,
486 				      static_cast<const QVector<Vec2f> >(tailTexCoordArr), tailIndices, static_cast<const QVector<Vec3f> >(dusttailColorArr));
487 		sPainter.drawStelVertexArray(vaDust, true);
488 	}
489 	sPainter.setBlending(false);
490 }
491 
drawComa(StelCore * core,StelProjector::ModelViewTranformP transfo)492 void Comet::drawComa(StelCore* core, StelProjector::ModelViewTranformP transfo)
493 {
494 	// Find rotation matrix from 0/0/1 to viewdirection! crossproduct for axis (normal vector), dotproduct for angle.
495 	Vec3d eclposNrm=eclipticPos+aberrationPush - core->getObserverHeliocentricEclipticPos()  ; eclposNrm.normalize();
496 	Mat4d comarot=Mat4d::rotation(Vec3d(0.0, 0.0, 1.0)^(eclposNrm), std::acos(Vec3d(0.0, 0.0, 1.0).dot(eclposNrm)) );
497 	StelProjector::ModelViewTranformP transfo2 = transfo->clone();
498 	transfo2->combine(comarot);
499 	StelPainter sPainter(core->getProjection(transfo2));
500 
501 	sPainter.setBlending(true, GL_ONE, GL_ONE);
502 
503 	StelToneReproducer* eye = core->getToneReproducer();
504 	float lum = core->getSkyDrawer()->surfaceBrightnessToLuminance(getVMagnitudeWithExtinction(core)+11.0f); // How to calibrate?
505 	// Get the luminance scaled between 0 and 1
506 	const float aLum =eye->adaptLuminanceScaled(lum);
507 	const float magFactor=qBound(0.25f*intensityFovScale, aLum*intensityFovScale, 2.0f);
508 	comaTexture->bind();
509 	sPainter.setColor(0.3f*magFactor,0.7f*magFactor,magFactor);
510 	StelVertexArray vaComa(static_cast<const QVector<Vec3d> >(comaVertexArr), StelVertexArray::Triangles, static_cast<const QVector<Vec2f> >(comaTexCoordArr));
511 	sPainter.drawStelVertexArray(vaComa, true);
512 	sPainter.setBlending(false);
513 }
514 
515 // Formula found at http://www.projectpluto.com/update7b.htm#comet_tail_formula
getComaDiameterAndTailLengthAU() const516 Vec2f Comet::getComaDiameterAndTailLengthAU() const
517 {
518 	const float r = static_cast<float>(getHeliocentricEclipticPos().length());
519 	const float mhelio = absoluteMagnitude + slopeParameter * log10(r);
520 	const float Do = powf(10.0f, ((-0.0033f*mhelio - 0.07f) * mhelio + 3.25f));
521 	const float common = 1.0f - powf(10.0f, (-2.0f*r));
522 	const float D = Do * common * (1.0f - powf(10.0f, -r)) * (1000.0f*AU_KMf);
523 	const float Lo = powf(10.0f, ((-0.0075f*mhelio - 0.19f) * mhelio + 2.1f));
524 	const float L = Lo*(1.0f-powf(10.0f, -4.0f*r)) * common * (1e6f*AU_KMf);
525 	return Vec2f(D, L);
526 }
527 
computeComa(const float diameter)528 void Comet::computeComa(const float diameter)
529 {
530 	StelPainter::computeFanDisk(0.5f*diameter, 3, 3, comaVertexArr, comaTexCoordArr);
531 }
532 
533 //! create parabola shell to represent a tail. Designed for slices=16, stacks=16, but should work with other sizes as well.
534 //! (Maybe slices must be an even number.)
535 // Parabola equation: z=x²/2p.
536 // xOffset for the dust tail, this may introduce a bend. Units are x per sqrt(z).
computeParabola(const float parameter,const float radius,const float zshift,QVector<Vec3d> & vertexArr,QVector<Vec2f> & texCoordArr,QVector<unsigned short> & indices,const float xOffset)537 void Comet::computeParabola(const float parameter, const float radius, const float zshift,
538 						  QVector<Vec3d>& vertexArr, QVector<Vec2f>& texCoordArr,
539 						  QVector<unsigned short> &indices, const float xOffset)
540 {
541 	// keep the array and replace contents. However, using replace() is only slightly faster.
542 	if (vertexArr.length() < static_cast<int>(((COMET_TAIL_SLICES*COMET_TAIL_STACKS+1))))
543 		vertexArr.resize((COMET_TAIL_SLICES*COMET_TAIL_STACKS+1));
544 	if (createTailIndices) indices.clear();
545 	if (createTailTextureCoords) texCoordArr.clear();
546 	// The parabola has triangular faces with vertices on two circles that are rotated against each other.
547 	float xa[2*COMET_TAIL_SLICES];
548 	float ya[2*COMET_TAIL_SLICES];
549 	float x, y, z;
550 
551 	// fill xa, ya with sin/cosines. TBD: make more efficient with index mirroring etc.
552 	float da=M_PIf/COMET_TAIL_SLICES; // full circle/2slices
553 	for (unsigned short int i=0; i<2*COMET_TAIL_SLICES; ++i){
554 		xa[i]=-sin(i*da);
555 		ya[i]=cos(i*da);
556 	}
557 
558 	vertexArr.replace(0, Vec3d(0.0, 0.0, static_cast<double>(zshift)));
559 	int vertexArrIndex=1;
560 	if (createTailTextureCoords) texCoordArr << Vec2f(0.5f, 0.5f);
561 	// define the indices lying on circles, starting at 1: odd rings have 1/slices+1/2slices, even-numbered rings straight 1/slices
562 	// inner ring#1
563 	for (unsigned short int ring=1; ring<=COMET_TAIL_STACKS; ++ring){
564 		z=ring*radius/COMET_TAIL_STACKS; z=z*z/(2*parameter) + zshift;
565 		const float xShift= xOffset*z*z;
566 		for (unsigned short int i=ring & 1; i<2*COMET_TAIL_SLICES; i+=2) { // i.e., ring1 has shifted vertices, ring2 has even ones.
567 			x=xa[i]*radius*ring/COMET_TAIL_STACKS;
568 			y=ya[i]*radius*ring/COMET_TAIL_STACKS;
569 			vertexArr.replace(vertexArrIndex++, Vec3d(static_cast<double>(x+xShift), static_cast<double>(y), static_cast<double>(z)));
570 			if (createTailTextureCoords) texCoordArr << Vec2f(0.5f+ 0.5f*x/radius, 0.5f+0.5f*y/radius);
571 		}
572 	}
573 	// now link the faces with indices.
574 	if (createTailIndices)
575 	{
576 		for (unsigned short i=1; i<COMET_TAIL_SLICES; ++i) indices << 0 << i << i+1;
577 		indices << 0 << COMET_TAIL_SLICES << 1; // close inner fan.
578 		// The other slices are a repeating pattern of 2 possibilities. Index @ring always is on the inner ring (slices-agon)
579 		for (unsigned short ring=1; ring<COMET_TAIL_STACKS; ring+=2) { // odd rings
580 			const unsigned short int first=(ring-1)*COMET_TAIL_SLICES+1;
581 			for (unsigned short int i=0; i<COMET_TAIL_SLICES-1; ++i){
582 				indices << first+i << first+COMET_TAIL_SLICES+i << first+COMET_TAIL_SLICES+1+i;
583 				indices << first+i << first+COMET_TAIL_SLICES+1+i << first+1+i;
584 			}
585 			// closing slice: mesh with other indices...
586 			indices << ring*COMET_TAIL_SLICES << (ring+1)*COMET_TAIL_SLICES << ring*COMET_TAIL_SLICES+1;
587 			indices << ring*COMET_TAIL_SLICES << ring*COMET_TAIL_SLICES+1 << first;
588 		}
589 
590 		for (unsigned short int ring=2; ring<COMET_TAIL_STACKS; ring+=2) { // even rings: different sequence.
591 			const unsigned short int first=(ring-1)*COMET_TAIL_SLICES+1;
592 			for (unsigned short int i=0; i<COMET_TAIL_SLICES-1; ++i){
593 				indices << first+i << first+COMET_TAIL_SLICES+i << first+1+i;
594 				indices << first+1+i << first+COMET_TAIL_SLICES+i << first+COMET_TAIL_SLICES+1+i;
595 			}
596 			// closing slice: mesh with other indices...
597 			indices << ring*COMET_TAIL_SLICES << (ring+1)*COMET_TAIL_SLICES << first;
598 			indices << first << (ring+1)*COMET_TAIL_SLICES << ring*COMET_TAIL_SLICES+1;
599 		}
600 	}
601 	createTailIndices=false;
602 	createTailTextureCoords=false;
603 }
604