1 /*
2  * Stellarium
3  * Copyright (C) 2002 Fabien Chereau
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  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 #include <QOpenGLFunctions_1_0>
21 #include "StelApp.hpp"
22 #include "StelCore.hpp"
23 #include "StelFileMgr.hpp"
24 #include "StelTexture.hpp"
25 #include "StelSkyDrawer.hpp"
26 #include "SolarSystem.hpp"
27 #include "LandscapeMgr.hpp"
28 #include "Planet.hpp"
29 #include "Orbit.hpp"
30 #include "planetsephems/precession.h"
31 #include "planetsephems/EphemWrapper.hpp"
32 #include "StelObserver.hpp"
33 #include "StelProjector.hpp"
34 #include "sidereal_time.h"
35 #include "StelTextureMgr.hpp"
36 #include "StelModuleMgr.hpp"
37 #include "StarMgr.hpp"
38 #include "StelMovementMgr.hpp"
39 #include "StelPainter.hpp"
40 #include "StelTranslator.hpp"
41 #include "StelUtils.hpp"
42 #include "StelOpenGL.hpp"
43 #include "StelOBJ.hpp"
44 #include "StelOpenGLArray.hpp"
45 #include "StelHips.hpp"
46 #include "RefractionExtinction.hpp"
47 #include "StelObjectMgr.hpp"
48 
49 #include <limits>
50 #include <QByteArray>
51 #include <QTextStream>
52 #include <QString>
53 #include <QDebug>
54 #include <QVarLengthArray>
55 #include <QOpenGLBuffer>
56 #include <QOpenGLContext>
57 #ifdef DEBUG_SHADOWMAP
58 #include <QOpenGLFramebufferObject>
59 #endif
60 #include <QOpenGLShader>
61 #include <QtConcurrent>
62 #include <QElapsedTimer>
63 
64 const QString Planet::PLANET_TYPE = QStringLiteral("Planet");
65 
66 bool Planet::shaderError = false;
67 
68 Vec3f Planet::labelColor = Vec3f(0.4f,0.4f,0.8f);
69 Vec3f Planet::orbitColor = Vec3f(1.0f,0.6f,1.0f);
70 Vec3f Planet::orbitMajorPlanetsColor = Vec3f(1.0f,0.6f,1.0f);
71 Vec3f Planet::orbitMoonsColor = Vec3f(1.0f,0.6f,1.0f);
72 Vec3f Planet::orbitMinorPlanetsColor = Vec3f(1.0f,0.6f,1.0f);
73 Vec3f Planet::orbitDwarfPlanetsColor = Vec3f(1.0f,0.6f,1.0f);
74 Vec3f Planet::orbitCubewanosColor = Vec3f(1.0f,0.6f,1.0f);
75 Vec3f Planet::orbitPlutinosColor = Vec3f(1.0f,0.6f,1.0f);
76 Vec3f Planet::orbitScatteredDiscObjectsColor = Vec3f(1.0f,0.6f,1.0f);
77 Vec3f Planet::orbitOortCloudObjectsColor = Vec3f(1.0f,0.6f,1.0f);
78 Vec3f Planet::orbitSednoidsColor = Vec3f(1.0f,0.6f,1.0f);
79 Vec3f Planet::orbitInterstellarColor = Vec3f(1.0f,0.2f,1.0f);
80 Vec3f Planet::orbitCometsColor = Vec3f(1.0f,0.6f,1.0f);
81 Vec3f Planet::orbitMercuryColor = Vec3f(1.0f,0.6f,1.0f);
82 Vec3f Planet::orbitVenusColor = Vec3f(1.0f,0.6f,1.0f);
83 Vec3f Planet::orbitEarthColor = Vec3f(1.0f,0.6f,1.0f);
84 Vec3f Planet::orbitMarsColor = Vec3f(1.0f,0.6f,1.0f);
85 Vec3f Planet::orbitJupiterColor = Vec3f(1.0f,0.6f,1.0f);
86 Vec3f Planet::orbitSaturnColor = Vec3f(1.0f,0.6f,1.0f);
87 Vec3f Planet::orbitUranusColor = Vec3f(1.0f,0.6f,1.0f);
88 Vec3f Planet::orbitNeptuneColor = Vec3f(1.0f,0.6f,1.0f);
89 StelTextureSP Planet::hintCircleTex;
90 StelTextureSP Planet::texEarthShadow;
91 
92 bool Planet::drawMoonHalo = true;
93 bool Planet::drawSunHalo = true;
94 bool Planet::permanentDrawingOrbits = false;
95 Planet::PlanetOrbitColorStyle Planet::orbitColorStyle = Planet::ocsOneColor;
96 
97 int Planet::orbitsThickness = 1;
98 
99 QOpenGLShaderProgram* Planet::planetShaderProgram=Q_NULLPTR;
100 Planet::PlanetShaderVars Planet::planetShaderVars;
101 QOpenGLShaderProgram* Planet::ringPlanetShaderProgram=Q_NULLPTR;
102 Planet::PlanetShaderVars Planet::ringPlanetShaderVars;
103 QOpenGLShaderProgram* Planet::moonShaderProgram=Q_NULLPTR;
104 Planet::PlanetShaderVars Planet::moonShaderVars;
105 QOpenGLShaderProgram* Planet::objShaderProgram=Q_NULLPTR;
106 Planet::PlanetShaderVars Planet::objShaderVars;
107 QOpenGLShaderProgram* Planet::objShadowShaderProgram=Q_NULLPTR;
108 Planet::PlanetShaderVars  Planet::objShadowShaderVars;
109 QOpenGLShaderProgram* Planet::transformShaderProgram=Q_NULLPTR;
110 Planet::PlanetShaderVars Planet::transformShaderVars;
111 
112 bool Planet::shadowInitialized = false;
113 Vec2f Planet::shadowPolyOffset = Vec2f(0.0f, 0.0f);
114 #ifdef DEBUG_SHADOWMAP
115 QOpenGLFramebufferObject* Planet::shadowFBO = Q_NULLPTR;
116 #else
117 GLuint Planet::shadowFBO = 0;
118 #endif
119 GLuint Planet::shadowTex = 0;
120 
121 const QMap<Planet::PlanetType, QString> Planet::pTypeMap = // Maps type to english name.
122 {
123 	{Planet::isStar,	"star"},
124 	{Planet::isPlanet,	"planet"},
125 	{Planet::isMoon,	"moon"},
126 	{Planet::isObserver,	"observer"},
127 	{Planet::isArtificial,	"artificial"},
128 	{Planet::isAsteroid,	"asteroid"},
129 	{Planet::isPlutino,	"plutino"},
130 	{Planet::isComet,	"comet"},
131 	{Planet::isDwarfPlanet,	"dwarf planet"},
132 	{Planet::isCubewano,	"cubewano"},
133 	{Planet::isSDO,		"scattered disc object"},
134 	{Planet::isOCO,		"Oort cloud object"},
135 	{Planet::isSednoid,	"sednoid"},
136 	{Planet::isInterstellar,"interstellar object"},
137 	{Planet::isUNDEFINED,	"UNDEFINED"} // something must be broken before we ever see this!
138 };
139 
140 const QMap<Planet::ApparentMagnitudeAlgorithm, QString> Planet::vMagAlgorithmMap =
141 {
142 	{Planet::MallamaHilton_2018,	        "Mallama2018"},
143 	{Planet::ExplanatorySupplement_2013,	"ExpSup2013"},
144 	{Planet::ExplanatorySupplement_1992,	"ExpSup1992"},
145 	{Planet::Mueller_1893,			"Mueller1893"},
146 	{Planet::AstronomicalAlmanac_1984,	"AstrAlm1984"},
147 	{Planet::Generic,			"Generic"},
148 	{Planet::UndefinedAlgorithm,		""}
149 };
150 
151 Planet::ApparentMagnitudeAlgorithm Planet::vMagAlgorithm;
152 
153 
154 #define STRINGIFY2(a) #a
155 #define STRINGIFY(a) STRINGIFY2(a)
156 #define SM_SIZE 1024
157 
PlanetOBJModel()158 Planet::PlanetOBJModel::PlanetOBJModel()
159 	: needsRescale(true), projPosBuffer(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)), obj(new StelOBJ()), arr(new StelOpenGLArray())
160 {
161 	//The buffer is refreshed completely before each draw, so StreamDraw should be ok
162 	projPosBuffer->setUsagePattern(QOpenGLBuffer::StreamDraw);
163 }
164 
~PlanetOBJModel()165 Planet::PlanetOBJModel::~PlanetOBJModel()
166 {
167 	delete projPosBuffer;
168 	delete obj;
169 	delete arr;
170 }
171 
loadGL()172 bool Planet::PlanetOBJModel::loadGL()
173 {
174 	if(arr->load(obj,false))
175 	{
176 		//delete StelOBJ because the data is no longer needed
177 		delete obj;
178 		obj = Q_NULLPTR;
179 		//make sure the vector has enough space to hold the projected data
180 		projectedPosArray.resize(posArray.size());
181 		//create the GL buffer for the projection
182 		return projPosBuffer->create();
183 	}
184 	return false;
185 }
186 
performScaling(double scale)187 void Planet::PlanetOBJModel::performScaling(double scale)
188 {
189 	scaledArray = posArray;
190 
191 	//pre-scale the cpu-side array
192 	float aScale=static_cast<float>(scale);
193 	for(int i = 0; i<posArray.size();++i)
194 	{
195 		scaledArray[i]*=aScale;
196 	}
197 
198 	needsRescale = false;
199 }
200 
Planet(const QString & englishName,double radius,double oblateness,Vec3f halocolor,float albedo,float roughness,const QString & atexMapName,const QString & anormalMapName,const QString & aobjModelName,posFuncType coordFunc,Orbit * anOrbitPtr,OsculatingFunctType * osculatingFunc,bool acloseOrbit,bool hidden,bool hasAtmosphere,bool hasHalo,const QString & pTypeStr)201 Planet::Planet(const QString& englishName,
202 	       double radius,
203 	       double oblateness,
204 	       Vec3f halocolor,
205 	       float albedo,
206 	       float roughness,
207 	       const QString& atexMapName,
208 	       const QString& anormalMapName,
209 	       const QString& aobjModelName,
210 	       posFuncType coordFunc,
211 	       Orbit* anOrbitPtr,
212 	       OsculatingFunctType *osculatingFunc,
213 	       bool acloseOrbit,
214 	       bool hidden,
215 	       bool hasAtmosphere,
216 	       bool hasHalo,
217 	       const QString& pTypeStr)
218 	: flagNativeName(true),
219 	  deltaJDE(StelCore::JD_SECOND),
220 	  deltaOrbitJDE(0.0),
221 	  closeOrbit(acloseOrbit),
222 	  englishName(englishName),
223 	  nameI18(englishName),
224 	  nativeName(""),
225 	  texMapName(atexMapName),
226 	  normalMapName(anormalMapName),
227 	  siderealPeriod(0.),
228 	  equatorialRadius(radius),
229 	  oneMinusOblateness(1.0-oblateness),
230 	  eclipticPos(0.,0.,0.),
231 	  eclipticVelocity(0.,0.,0.),
232 	  aberrationPush(0.,0.,0.),
233 	  haloColor(halocolor),
234 	  absoluteMagnitude(-99.0f),
235 	  albedo(albedo),
236 	  roughness(roughness),
237 	  outgas_intensity(0.f),
238 	  outgas_falloff(0.f),
239 	  rotLocalToParent(Mat4d::identity()),
240 	  axisRotation(0.f),
241 	  objModel(Q_NULLPTR),
242 	  objModelLoader(Q_NULLPTR),
243 	  survey(Q_NULLPTR),
244 	  rings(Q_NULLPTR),
245 	  distance(0.0),
246 	  sphereScale(1.),
247 	  lastJDE(J2000),
248 	  coordFunc(coordFunc),
249 	  orbitPtr(anOrbitPtr),
250 	  osculatingFunc(osculatingFunc),
251 	  parent(Q_NULLPTR),
252 	  flagLabels(true),
253 	  hidden(hidden),
254 	  atmosphere(hasAtmosphere),
255 	  halo(hasHalo),
256 	  multisamplingEnabled_(StelApp::getInstance().getSettings()->value("video/multisampling", 0).toUInt() != 0),
257 	  gl(Q_NULLPTR),
258 	  iauMoonNumber(""),
259 	  orbitPositionsCache(ORBIT_SEGMENTS * 2)
260 {
261 	// Initialize pType with the key found in pTypeMap, or mark planet type as undefined.
262 	// The latter condition should obviously never happen.
263 	pType = pTypeMap.key(pTypeStr, Planet::isUNDEFINED);
264 	if (pType==Planet::isUNDEFINED)
265 	{
266 		qCritical() << "Planet " << englishName << "has no type. Please edit one of ssystem_major.ini or ssystem_minor.ini to ensure operation.";
267 		exit(-1);
268 	}
269 	Q_ASSERT(pType != Planet::isUNDEFINED);
270 
271 	//only try loading textures when there is actually something to load!
272 	//prevents some overhead when starting
273 	texMapFileOrig = QString();
274 	if(!texMapName.isEmpty())
275 	{
276 		// TODO: use StelFileMgr::findFileInAllPaths() after introducing an Add-On Manager
277 		QString texMapFile = StelFileMgr::findFile("textures/"+texMapName, StelFileMgr::File);
278 		if (!texMapFile.isEmpty())
279 		{
280 			texMap = StelApp::getInstance().getTextureManager().createTextureThread(texMapFile, StelTexture::StelTextureParams(true, GL_LINEAR, GL_REPEAT));
281 			texMapFileOrig = texMapFile;
282 		}
283 		else
284 			qWarning()<<"Cannot resolve path to texture file"<<texMapName<<"of object"<<englishName;
285 	}
286 
287 	normalMapFileOrig = QString();
288 	if(!normalMapName.isEmpty())
289 	{
290 		// TODO: use StelFileMgr::findFileInAllPaths() after introducing an Add-On Manager
291 		QString normalMapFile = StelFileMgr::findFile("textures/"+normalMapName, StelFileMgr::File);
292 		if (!normalMapFile.isEmpty())
293 		{
294 			normalMap = StelApp::getInstance().getTextureManager().createTextureThread(normalMapFile, StelTexture::StelTextureParams(true, GL_LINEAR, GL_REPEAT));
295 			normalMapFileOrig = normalMapFile;
296 		}
297 	}
298 	//the OBJ is lazily loaded when first required
299 	if(!aobjModelName.isEmpty())
300 	{
301 		// TODO: use StelFileMgr::findFileInAllPaths() after introducing an Add-On Manager
302 		objModelPath = StelFileMgr::findFile("models/"+aobjModelName, StelFileMgr::File);
303 		if(objModelPath.isEmpty())
304 		{
305 			qWarning()<<"Cannot resolve path to model file"<<aobjModelName<<"of object"<<englishName;
306 		}
307 	}
308 	if ((pType <= isDwarfPlanet) && (englishName!="Pluto")) // concentrate on "inner" objects, KBO etc. stay at 1/s recomputation.
309 	{
310 		deltaJDE = 0.001*StelCore::JD_SECOND;
311 	}
312 	propMgr = StelApp::getInstance().getStelPropertyManager();
313 
314 	Q_ASSERT_X(oneMinusOblateness<=1., "Planet.cpp", QString("1-oblateness too large: %1").arg(QString::number(oneMinusOblateness, 'f', 10)).toLatin1() );
315 }
316 
317 // called in SolarSystem::init() before first planet is created. May initialize static variables.
init()318 void Planet::init()
319 {
320 	RotationElements::updatePlanetCorrections(J2000, RotationElements::EarthMoon);
321 	RotationElements::updatePlanetCorrections(J2000, RotationElements::Mars);
322 	RotationElements::updatePlanetCorrections(J2000, RotationElements::Jupiter);
323 	RotationElements::updatePlanetCorrections(J2000, RotationElements::Saturn);
324 	RotationElements::updatePlanetCorrections(J2000, RotationElements::Uranus);
325 	RotationElements::updatePlanetCorrections(J2000, RotationElements::Neptune);
326 }
327 
~Planet()328 Planet::~Planet()
329 {
330 	delete rings;
331 	delete objModel;
332 }
333 
resetTextures()334 void Planet::resetTextures()
335 {
336 	// restore texture
337 	if (!texMapFileOrig.isEmpty())
338 		texMap = StelApp::getInstance().getTextureManager().createTextureThread(texMapFileOrig, StelTexture::StelTextureParams(true, GL_LINEAR, GL_REPEAT));
339 
340 	// restore normal map
341 	if (!normalMapFileOrig.isEmpty())
342 		normalMap = StelApp::getInstance().getTextureManager().createTextureThread(normalMapFileOrig, StelTexture::StelTextureParams(true, GL_LINEAR, GL_REPEAT));
343 }
344 
replaceTexture(const QString & texName)345 void Planet::replaceTexture(const QString &texName)
346 {
347 	if(!texName.isEmpty())
348 	{
349 		QString texMapFile = StelFileMgr::findFile("scripts/" + texName, StelFileMgr::File);
350 		if (!texMapFile.isEmpty())
351 			texMap = StelApp::getInstance().getTextureManager().createTextureThread(texMapFile, StelTexture::StelTextureParams(true, GL_LINEAR, GL_REPEAT));
352 		else
353 			qWarning()<<"Cannot resolve path to texture file"<<texName<<"of object"<<englishName;
354 
355 	}
356 }
357 
translateName(const StelTranslator & trans)358 void Planet::translateName(const StelTranslator& trans)
359 {
360 	nameI18 = trans.qtranslate(englishName, getContextString());
361 	nativeNameMeaningI18n = (!nativeNameMeaning.isEmpty() ? trans.qtranslate(nativeNameMeaning) : "");
362 }
363 
setIAUMoonNumber(QString designation)364 void Planet::setIAUMoonNumber(QString designation)
365 {
366 	if (!iauMoonNumber.isEmpty())
367 		return;
368 
369 	iauMoonNumber = designation;
370 }
371 
getEnglishName() const372 QString Planet::getEnglishName() const
373 {
374     if (!iauMoonNumber.isEmpty())
375         return QString("%1 (%2)").arg(englishName).arg(iauMoonNumber);
376     else
377         return englishName;
378 }
379 
getNameI18n() const380 QString Planet::getNameI18n() const
381 {
382     if (!iauMoonNumber.isEmpty())
383         return QString("%1 (%2)").arg(nameI18).arg(iauMoonNumber);
384     else
385         return nameI18;
386 }
387 
getContextString() const388 const QString Planet::getContextString() const
389 {
390 	QString context = "";
391 	switch (getPlanetType())
392 	{
393 		case isStar:
394 			context = "star";
395 			break;
396 		case isPlanet:
397 			context = "major planet";
398 			break;
399 		case isMoon:
400 			context = "moon";
401 			break;
402 		case isObserver:
403 		case isArtificial:
404 			context = "special celestial body";
405 			break;
406 		case isAsteroid:
407 		case isPlutino:
408 		case isDwarfPlanet:
409 		case isCubewano:
410 		case isSDO:
411 		case isOCO:
412 		case isSednoid:
413 		case isInterstellar:
414 			context = "minor planet";
415 			break;
416 		case isComet:
417 			context = "comet";
418 			break;
419 		default:
420 			context = "";
421 			break;
422 	}
423 
424 	return context;
425 }
426 
getPlanetLabel() const427 QString Planet::getPlanetLabel() const
428 {
429 	QString str;
430 	QTextStream oss(&str);
431 	if (englishName=="Pluto") // We must prepend minor planet number here. Actually Dwarf Planet Pluto is still a "Planet" object in Stellarium...
432 		oss << QString("(134340) ");
433 
434 	if (getFlagNativeName())
435 	{
436 		switch (propMgr->getStelPropertyValue("ConstellationMgr.constellationDisplayStyle").toInt())
437 		{
438 			case 1: // constellationsNative
439 				oss << (nativeName.isEmpty() ? getNameI18n() : QString("%1 [%2]").arg(getNativeName(), getNameI18n()));
440 				break;
441 			case 2: // constellationsTranslated
442 				oss << (nativeNameMeaningI18n.isEmpty() ? getNameI18n() : QString("%1 [%2]").arg(getNativeNameI18n(), getNameI18n()));
443 				break;
444 			case 3: // constellationsEnglish
445 				oss << (nativeNameMeaning.isEmpty() ? getEnglishName() : QString("%1 [%2]").arg(nativeNameMeaning, getEnglishName()));
446 				break;
447 			default:
448 				oss << getNameI18n();
449 				break;
450 		}
451 	}
452 	else
453 	{
454 		switch (propMgr->getStelPropertyValue("ConstellationMgr.constellationDisplayStyle").toInt())
455 		{
456 			case 3: // constellationsEnglish
457 				oss << getEnglishName();
458 				break;
459 			case 1: // constellationsNative
460 			case 2: // constellationsTranslated
461 			default:
462 				oss << getNameI18n();
463 				break;
464 		}
465 	}
466 
467 	oss.setRealNumberNotation(QTextStream::FixedNotation);
468 	oss.setRealNumberPrecision(1);
469 	if (sphereScale != 1.)
470 		oss << QString::fromUtf8(" (\xC3\x97") << sphereScale << ")";
471 
472 	return str;
473 }
474 
getInfoStringName(const StelCore * core,const InfoStringGroup & flags) const475 QString Planet::getInfoStringName(const StelCore *core, const InfoStringGroup& flags) const
476 {
477 	Q_UNUSED(core) Q_UNUSED(flags)
478 	QString str;
479 	QTextStream oss(&str);
480 	oss << "<h2>" << getPlanetLabel() << "</h2>";
481 	return str;
482 }
483 
getInfoStringAbsoluteMagnitude(const StelCore * core,const InfoStringGroup & flags) const484 QString Planet::getInfoStringAbsoluteMagnitude(const StelCore *core, const InfoStringGroup& flags) const
485 {
486 	Q_UNUSED(core)
487 	QString str;
488 	QTextStream oss(&str);
489 	if (flags&AbsoluteMagnitude && (getAbsoluteMagnitude() > -99.f))
490 	{
491 		oss << QString("%1: %2<br/>").arg(q_("Absolute Magnitude")).arg(getAbsoluteMagnitude(), 0, 'f', 2);
492 		const float moMag=getMeanOppositionMagnitude();
493 		if (moMag<50.f)
494 			oss << QString("%1: %2<br/>").arg(q_("Mean Opposition Magnitude")).arg(moMag, 0, 'f', 2);
495 	}
496 
497 	return str;
498 }
499 
500 // Return the information string "ready to print" :)
getInfoString(const StelCore * core,const InfoStringGroup & flags) const501 QString Planet::getInfoString(const StelCore* core, const InfoStringGroup& flags) const
502 {
503 	QString str;
504 	QTextStream oss(&str);
505 	const double distanceAu = getJ2000EquatorialPos(core).length();
506 
507 	if (flags&Name)
508 	{
509 		oss << getInfoStringName(core, flags);
510 
511 		QStringList extraNames=getExtraInfoStrings(Name);
512 		if (extraNames.length()>0)
513 			oss << q_("Additional names: ") << extraNames.join(", ") << "<br/>";
514 	}
515 
516 	if (flags&CatalogNumber)
517 	{
518 		QStringList extraCat=getExtraInfoStrings(CatalogNumber);
519 		if (extraCat.length()>0)
520 			oss << q_("Additional catalog numbers: ") << extraCat.join(", ") << "<br/>";
521 	}
522 
523 	if (flags&ObjectType && getPlanetType()!=isUNDEFINED)
524 	{
525 		if (getPlanetType()==isComet)
526 		{
527 			const QString cometType = (static_cast<KeplerOrbit*>(orbitPtr)->getEccentricity() < 1.0) ?
528 						qc_("periodic", "type of comet") :
529 						qc_("non-periodic", "type of comet");
530 			oss << QString("%1: <b>%2</b> (%3)<br/>").arg(q_("Type"), q_(getPlanetTypeString()), cometType);
531 		}
532 		else
533 			oss << QString("%1: <b>%2</b><br/>").arg(q_("Type"), q_(getPlanetTypeString()));
534 	}
535 
536 	if (flags&Magnitude)
537 	{
538 		static const QMap<ApparentMagnitudeAlgorithm, int>decMap={
539 			{ Mueller_1893,               1 },
540 			{ AstronomicalAlmanac_1984,   1 },
541 			{ ExplanatorySupplement_1992, 1 },
542 			{ ExplanatorySupplement_2013, 2 },
543 			{ MallamaHilton_2018,         2 }};
544 		if (!fuzzyEquals(getVMagnitude(core), std::numeric_limits<float>::infinity()))
545 			oss << getMagnitudeInfoString(core, flags, decMap.value(vMagAlgorithm, 1));
546 		oss << getExtraInfoStrings(Magnitude).join("");
547 	}
548 
549 	if (flags&AbsoluteMagnitude)
550 	{
551 		oss << getInfoStringAbsoluteMagnitude(core, flags);
552 		oss << getExtraInfoStrings(AbsoluteMagnitude).join("");
553 	}
554 
555 	oss << getInfoStringExtraMag(core, flags);
556 	oss << getCommonInfoString(core, flags);
557 
558 #ifndef NDEBUG
559 	// Debug help.
560 	//oss << "Apparent Magnitude Algorithm: " << getApparentMagnitudeAlgorithmString() << " " << vMagAlgorithm << "<br>";
561 	Vec3d sunAberr=GETSTELMODULE(SolarSystem)->getSun()->eclipticPos  +GETSTELMODULE(SolarSystem)->getSun()->getAberrationPush()    -GETSTELMODULE(SolarSystem)->getEarth()->eclipticPos;
562 	double lon, lat;
563 	StelUtils::rectToSphe(&lon, &lat, sunAberr);
564 	oss << "Sun (light time and aberration corrected) at &lambda;=" << StelUtils::radToDmsStr(StelUtils::fmodpos(lon, 2.*M_PI)) << " &beta;=" << StelUtils::radToDmsStr(lat) << "<br>";
565 	// This is mostly for debugging. Maybe also useful for letting people use our results to cross-check theirs, but we should not act as reference, currently...
566 	// maybe separate this out into:
567 	//if (flags&EclipticCoordXYZ)
568 	// For now: add to EclipticCoordJ2000 group
569 	if (flags&EclipticCoordJ2000)
570 	{
571 		QString algoName("VSOP87");
572 		if (EphemWrapper::use_de440(core->getJDE())) algoName="DE440";
573 		else if (EphemWrapper::use_de441(core->getJDE())) algoName="DE441";
574 		else if (EphemWrapper::use_de430(core->getJDE())) algoName="DE430";
575 		else if (EphemWrapper::use_de431(core->getJDE())) algoName="DE431";
576 		else if (pType>=isAsteroid) algoName="Keplerian"; // TODO: observer/artificial?
577 		// TRANSLATORS: Ecliptical rectangular coordinates
578 		oss << QString("%1 XYZ J2000.0 (%2) without aberration: %3/%4/%5 AU").arg(qc_("Ecliptical","coordinates"), algoName, QString::number(eclipticPos[0], 'f', 7), QString::number(eclipticPos[1], 'f', 7), QString::number(eclipticPos[2], 'f', 7)) << "<br>";
579 		Vec3d eclAb=eclipticPos+aberrationPush;
580 		oss << QString("%1 XYZ J2000.0 (%2) with aberration: %3/%4/%5 AU").arg(qc_("Ecliptical","coordinates"), algoName, QString::number(eclAb[0], 'f', 7), QString::number(eclAb[1], 'f', 7), QString::number(eclAb[2], 'f', 7)) << "<br>";
581 	}
582 #endif
583 
584 	// Second test avoids crash when observer is on spaceship
585 	if (flags&ProperMotion && !core->getCurrentObserver()->isObserverLifeOver())
586 	{
587 		const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
588 		// Setting/resetting the time causes a significant slowdown. We must apply some trickery to keep time in sync.
589 		const Vec3d equPos=getEquinoxEquatorialPos(core);
590 		double dec_equ, ra_equ;
591 		StelUtils::rectToSphe(&ra_equ,&dec_equ,equPos);
592 		StelCore* core1 = StelApp::getInstance().getCore(); // we need non-const reference here.
593 		const double currentJD=core1->getJDOfLastJDUpdate();
594 		const qint64 millis=core1->getMilliSecondsOfLastJDUpdate();
595 		core1->setJD(currentJD-StelCore::JD_HOUR);
596 		core1->update(0);
597 		Vec3d equPosPrev=getEquinoxEquatorialPos(core1);
598 		const double deltaEq=equPos.angle(equPosPrev);
599 		double dec_equPrev, ra_equPrev;
600 		StelUtils::rectToSphe(&ra_equPrev,&dec_equPrev,equPosPrev);
601 		double pa=atan2(ra_equ-ra_equPrev, dec_equ-dec_equPrev); // position angle: From North counterclockwise!
602 		if (pa<0) pa += 2.*M_PI;
603 		oss << QString("%1: %2 %3 %4%5<br/>").arg(q_("Hourly motion"), withDecimalDegree ? StelUtils::radToDecDegStr(deltaEq) : StelUtils::radToDmsStr(deltaEq), qc_("towards", "into the direction of"), QString::number(pa*M_180_PI, 'f', 1), QChar(0x00B0));
604 		oss << QString("%1: d&alpha;=%2 d&delta;=%3<br/>").arg(q_("Hourly motion"), withDecimalDegree ? StelUtils::radToDecDegStr(ra_equ-ra_equPrev) : StelUtils::radToDmsStr(ra_equ-ra_equPrev), withDecimalDegree ? StelUtils::radToDecDegStr(dec_equ-dec_equPrev) : StelUtils::radToDmsStr(dec_equ-dec_equPrev));
605 		core1->setJD(currentJD); // this calls sync() which sets millis
606 		core1->setMilliSecondsOfLastJDUpdate(millis); // restore millis.
607 		core1->update(0);
608 	}
609 
610 	oss << getInfoStringEloPhase(core, flags, pType<=isMoon);
611 
612 
613 	if (flags&Distance)
614 	{
615 		const double hdistanceAu = getHeliocentricEclipticPos().length();
616 		const double hdistanceKm = AU * hdistanceAu;
617 		// TRANSLATORS: Unit of measure for distance - astronomical unit
618 		QString au = qc_("AU", "distance, astronomical unit");
619 		// TRANSLATORS: Unit of measure for distance - kilometers
620 		QString km = qc_("km", "distance");
621 		QString distAU, distKM;
622 		if (englishName!="Sun")
623 		{
624 			if (hdistanceAu < 0.1)
625 			{
626 				distAU = QString::number(hdistanceAu, 'f', 6);
627 				distKM = QString::number(hdistanceKm, 'f', 3);
628 			}
629 			else
630 			{
631 				distAU = QString::number(hdistanceAu, 'f', 3);
632 				distKM = QString::number(hdistanceKm / 1.0e6, 'f', 3);
633 				// TRANSLATORS: Unit of measure for distance - millions of kilometers
634 				km = qc_("M km", "distance");
635 			}
636 
637 			oss << QString("%1: %2 %3 (%4 %5)<br/>").arg(q_("Distance from Sun"), distAU, au, distKM, km);
638 		}
639 		const double distanceKm = AU * distanceAu;
640 		if (distanceAu < 0.1)
641 		{
642 			distAU = QString::number(distanceAu, 'f', 6);
643 			distKM = QString::number(distanceKm, 'f', 3);
644 			// TRANSLATORS: Unit of measure for distance - kilometers
645 			km = qc_("km", "distance");
646 		}
647 		else
648 		{
649 			distAU = QString::number(distanceAu, 'f', 3);
650 			distKM = QString::number(distanceKm / 1.0e6, 'f', 3);
651 			// TRANSLATORS: Unit of measure for distance - millions of kilometers
652 			km = qc_("M km", "distance");
653 		}
654 
655 		oss << QString("%1: %2 %3 (%4 %5)<br/>").arg(q_("Distance"), distAU, au, distKM, km);
656 		// TRANSLATORS: Distance measured in terms of the speed of light
657 		oss << QString("%1: %2 <br/>").arg(q_("Light time"), StelUtils::hoursToHmsStr(distanceKm/SPEED_OF_LIGHT/3600.) );
658 		oss << getExtraInfoStrings(Distance).join("");
659 	}
660 
661 	if (flags&Velocity)
662 	{
663 		// TRANSLATORS: Unit of measure for speed - kilometers per second
664 		QString kms = qc_("km/s", "speed");
665 
666 		const Vec3d orbitalVel=getEclipticVelocity();
667 		const double orbVel=orbitalVel.length();
668 		if (orbVel>0.)
669 		{ // AU/d * km/AU /24
670 			const double orbVelKms=orbVel* AU/86400.;
671 			oss << QString("%1: %2 %3<br/>").arg(q_("Orbital velocity")).arg(orbVelKms, 0, 'f', 3).arg(kms);
672 			const double helioVel=getHeliocentricEclipticVelocity().length();
673 			if (!fuzzyEquals(helioVel, orbVel))
674 				oss << QString("%1: %2 %3<br/>").arg(q_("Heliocentric velocity")).arg(helioVel* AU/86400., 0, 'f', 3).arg(kms);
675 		}
676 		oss << getExtraInfoStrings(Velocity).join("");
677 	}
678 	oss << getInfoStringPeriods(core, flags);
679 	oss << getInfoStringSize(core, flags);
680 	oss << getInfoStringExtra(core, flags);
681 	oss << getSolarLunarInfoString(core, flags);
682 	if (!hasValidPositionalData(core->getJDE(), PositionQuality::Position))
683 	{
684 	    oss << q_("NOTE: orbital elements outdated -- consider updating!") << "<br/>";
685 	}
686 	postProcessInfoString(str, flags);
687 	return str;
688 }
689 
690 // Print apparent and equatorial diameters
getInfoStringSize(const StelCore * core,const InfoStringGroup & flags) const691 QString Planet::getInfoStringSize(const StelCore *core, const InfoStringGroup& flags) const
692 {
693 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
694 	QString str;
695 	QTextStream oss(&str);
696 
697 	const double angularSize = 2.*getAngularSize(core)*M_PI_180;
698 	if (flags&Size && angularSize>=4.8e-8)
699 	{
700 		QString s1, s2, sizeStr = "";
701 		if (rings)
702 		{
703 			const double withoutRings = 2.*getSpheroidAngularSize(core)*M_PI/180.;
704 			if (withDecimalDegree)
705 			{
706 				s1 = StelUtils::radToDecDegStr(withoutRings, 5, false, true);
707 				s2 = StelUtils::radToDecDegStr(angularSize, 5, false, true);
708 			}
709 			else
710 			{
711 				s1 = StelUtils::radToDmsPStr(withoutRings, 2);
712 				s2 = StelUtils::radToDmsPStr(angularSize, 2);
713 			}
714 
715 			sizeStr = QString("%1, %2: %3").arg(s1, q_("with rings"), s2);
716 		}
717 		else
718 		{
719 			if (sphereScale!=1.) // We must give correct diameters even if upscaling (e.g. Moon)
720 			{
721 				if (withDecimalDegree)
722 				{
723 					s1 = StelUtils::radToDecDegStr(angularSize / sphereScale, 5, false, true);
724 					s2 = StelUtils::radToDecDegStr(angularSize, 5, false, true);
725 				}
726 				else
727 				{
728 					s1 = StelUtils::radToDmsPStr(angularSize / sphereScale, 2);
729 					s2 = StelUtils::radToDmsPStr(angularSize, 2);
730 				}
731 
732 				sizeStr = QString("%1, %2: %3").arg(s1, q_("scaled up to"), s2);
733 			}
734 			else
735 			{
736 				if (withDecimalDegree)
737 					sizeStr = StelUtils::radToDecDegStr(angularSize, 5, false, true);
738 				else
739 					sizeStr = StelUtils::radToDmsPStr(angularSize, 2);
740 			}
741 		}
742 		oss << QString("%1: %2<br/>").arg(q_("Apparent diameter"), sizeStr);
743 	}
744 
745 	if (flags&Size)
746 	{
747 		QString diam = (getPlanetType()==isPlanet ? q_("Equatorial diameter") : q_("Diameter")); // Many asteroids have irregular shape (Currently unhandled)
748 		oss << QString("%1: %2 %3<br/>").arg(diam, QString::number(AU * getEquatorialRadius() * 2.0, 'f', 1) , qc_("km", "distance"));
749 		oss << getExtraInfoStrings(Size).join("");
750 	}
751 	return str;
752 }
753 
getInfoStringExtraMag(const StelCore * core,const InfoStringGroup & flags) const754 QString Planet::getInfoStringExtraMag(const StelCore *core, const InfoStringGroup& flags) const
755 {
756 	Q_UNUSED(core) Q_UNUSED(flags)
757 	return "";
758 }
759 
getInfoStringEloPhase(const StelCore * core,const InfoStringGroup & flags,const bool withIllum) const760 QString Planet::getInfoStringEloPhase(const StelCore *core, const InfoStringGroup& flags, const bool withIllum) const
761 {
762 	QString str;
763 	QTextStream oss(&str);
764 	if ((flags&Elongation) && (englishName!="Sun") && (core->getCurrentPlanet()->englishName!="Sun"))
765 	{
766 		const bool withTables = StelApp::getInstance().getFlagUseFormattingOutput();
767 		const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
768 		const Vec3d& observerHelioPos = core->getObserverHeliocentricEclipticPos();
769 		const double elongation = getElongation(observerHelioPos);
770 
771 		// some users require not "modern elongation" but just the DeltaLambda (GH:#1786)
772 		static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
773 		double raSun, deSun, ra, de, lSun, ecLong, bSun, ecLat;
774 		double obl=ssystem->getEarth()->getRotObliquity(core->getJDE());
775 		if (core->getUseNutation())
776 		{
777 			double dEps, dPsi;
778 			getNutationAngles(core->getJDE(), &dPsi, &dEps);
779 			obl+=dEps;
780 		}
781 		StelUtils::rectToSphe(&raSun, &deSun, ssystem->getSun()->getEquinoxEquatorialPos(core));
782 		StelUtils::rectToSphe(&ra, &de, getEquinoxEquatorialPos(core));
783 		StelUtils::equToEcl(raSun, deSun, obl, &lSun, &bSun);
784 		StelUtils::equToEcl(ra, de, obl, &ecLong, &ecLat);
785 		double elongAlongEcliptic=StelUtils::fmodpos(ecLong-lSun, M_PI*2.);
786 		if (elongAlongEcliptic > M_PI) elongAlongEcliptic-=2.*M_PI;
787 
788 		QString pha, elo, dLam;
789 		if (withDecimalDegree)
790 		{
791 			pha  = StelUtils::radToDecDegStr(getPhaseAngle(observerHelioPos),4,false,true);
792 			elo  = StelUtils::radToDecDegStr(elongation,4,false,true);
793 			dLam = StelUtils::radToDecDegStr(elongAlongEcliptic,4,false,true);
794 		}
795 		else
796 		{
797 			pha  = StelUtils::radToDmsStr(getPhaseAngle(observerHelioPos), true);
798 			elo  = StelUtils::radToDmsStr(elongation, true);
799 			dLam = StelUtils::radToDmsStr(elongAlongEcliptic, true);
800 		}
801 
802 		if (withTables)
803 		{
804 			oss << "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
805 			oss << QString("<tr><td>%1:</td><td align=\"right\">%2</td></tr>").arg(q_("Elongation"), elo);
806 			oss << QString("<tr><td>%1 (&Delta;&lambda;<sub>s</sub>):</td><td align=\"right\">%2</td></tr>").arg(q_("Elongation"), dLam);
807 			oss << QString("<tr><td>%1:</td><td align=\"right\">%2</td></tr>").arg(q_("Phase angle"), pha);
808 			if (withIllum)
809 				oss << QString("<tr><td>%1:</td><td align=\"right\">%2%</td></tr>").arg(q_("Illuminated")).arg(QString::number(getPhase(observerHelioPos) * 100., 'f', 1));
810 			oss << "</table>";
811 		}
812 		else
813 		{
814 			oss << QString("%1: %2<br/>").arg(q_("Elongation"), elo);
815 			oss << QString("%1: %2<br/>").arg(q_("Elong. in Ecl.Long."), dLam);
816 			oss << QString("%1: %2<br/>").arg(q_("Phase angle"), pha);
817 			if (withIllum)
818 				oss << QString("%1: %2%<br/>").arg(q_("Illuminated"), QString::number(getPhase(observerHelioPos) * 100., 'f', 1));
819 		}
820 
821 		if (getPlanetType()==isMoon && this->parent!=core->getCurrentPlanet())
822 		{
823 			QString ad;
824 			const double angularDistance = getJ2000EquatorialPos(core).angle(this->parent->getJ2000EquatorialPos(core));
825 			if (withDecimalDegree)
826 				ad = StelUtils::radToDecDegStr(angularDistance,4,false,true);
827 			else
828 				ad = StelUtils::radToDmsStr(angularDistance, true);
829 
830 			oss << QString("%1 %2 &mdash; %3: %4<br/>").arg(q_("Angular distance"), getNameI18n(), this->parent->getNameI18n(), ad);
831 		}
832 	}
833 	return str;
834 }
835 
getInfoStringPeriods(const StelCore * core,const InfoStringGroup & flags) const836 QString Planet::getInfoStringPeriods(const StelCore *core, const InfoStringGroup& flags) const
837 {
838 	QString str;
839 	QTextStream oss(&str);
840 
841 	if (flags&Extra)
842 	{
843 		const bool withTables = StelApp::getInstance().getFlagUseFormattingOutput();
844 		if (withTables)
845 			oss << "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
846 		const QByteArray fmt=withTables ? "<tr><td>%1:</td><td style='text-align: right;'> %2</td><td> %3 (%4 a)</td></tr>" : "%1: %2 %3 (%4 a)<br/>";
847 		// TRANSLATORS: Unit of measure for period - days
848 		QString days = qc_("days", "duration");
849 		// Sidereal and synodic periods are better sorted here
850 		PlanetP currentPlanet = core->getCurrentPlanet();
851 		const double siderealPeriod = getSiderealPeriod(); // days required for revolution around parent.
852 		const double siderealPeriodCurrentPlanet = currentPlanet->getSiderealPeriod();
853 		QString celestialObject = getEnglishName();
854 		if ((siderealPeriod>0.0) && (celestialObject != "Sun"))
855 		{
856 			// Sidereal (orbital) period for solar system bodies in days and in Julian years (symbol: a)
857 			oss << QString(fmt).arg(q_("Sidereal period"), QString::number(siderealPeriod, 'f', 2), days, QString::number(siderealPeriod/365.25, 'f', 3));
858 		}
859 		if (celestialObject!="Sun")
860 			celestialObject = getParent()->getEnglishName();
861 		// Synodic revolution period
862 		if (siderealPeriodCurrentPlanet > 0.0 && siderealPeriod > 0.0 && currentPlanet->getPlanetType()==Planet::isPlanet && (getPlanetType()==Planet::isPlanet || currentPlanet->getEnglishName()==celestialObject))
863 		{
864 			double synodicPeriod = qAbs(1/(1/siderealPeriodCurrentPlanet - 1/siderealPeriod));
865 			// Synodic period for major planets in days and in Julian years (symbol: a)
866 			oss << QString(fmt).arg(q_("Synodic period")).arg(QString::number(synodicPeriod, 'f', 2)).arg(days).arg(QString::number(synodicPeriod/365.25, 'f', 3));
867 		}
868 		if (withTables)
869 			oss << "</table>";
870 	}
871 	return str;
872 }
873 
874 class SolarEclipse
875 {
876 private:
877 	static constexpr double SunEarth = 109.12278; // ratio of Sun-Earth radius 696000/6378.1366
878 
879 public:
point(const StelCore * core)880 	static Vec3d point(const StelCore* core)
881 	{
882 		static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
883 		double raSun, deSun, raMoon, deMoon;
884 		StelUtils::rectToSphe(&raSun, &deSun, ssystem->getSun()->getEquinoxEquatorialPos(core));
885 		StelUtils::rectToSphe(&raMoon, &deMoon, ssystem->getMoon()->getEquinoxEquatorialPos(core));
886 
887 		double sdistanceAu = ssystem->getSun()->getEquinoxEquatorialPos(core).length();
888 		// Moon's distance in Earth's radius
889 		double mdistanceER = ssystem->getMoon()->getEquinoxEquatorialPos(core).length() * AU / 6378.1366;
890 		// Greenwich Apparent Sidereal Time
891 		double gast = get_apparent_sidereal_time(core->getJD(), core->getJDE());
892 
893 		if (raSun < 0.) raSun += M_PI * 2.;
894 		if (raMoon < 0.) raMoon += M_PI * 2.;
895 
896 		// Besselian elements
897 		// based on Explanatory supplement to the astronomical ephemeris
898 		// and the American ephemeris and nautical almanac (1961)
899 		const double rss = sdistanceAu * 23454.7925; // from 1 AU/Earth's radius : 149597870.8/6378.1366
900 		const double b = mdistanceER / rss;
901 		const double a = raSun - ((b * cos(deMoon) * (raMoon - raSun)) / ((1 - b) * cos(deSun)));
902 		const double d = deSun - (b * (deMoon - deSun) / (1 - b));
903 		double x = cos(deMoon) * sin((raMoon - a));
904 		x *= mdistanceER;
905 		double y = cos(d) * sin(deMoon);
906 		y -= cos(deMoon) * sin(d) * cos((raMoon - a));
907 		y *= mdistanceER;
908 		double z = sin(deMoon) * sin(d);
909 		z += cos(deMoon) * cos(d) * cos((raMoon - a));
910 		z *= mdistanceER;
911 		// parameters of the shadow cone
912 		const double f1 = asin((SunEarth + 0.272488) / (rss * (1 - b)));
913 		const double tf1 = tan(f1);
914 		const double f2 = asin((SunEarth - 0.272281) / (rss * (1 - b)));
915 		const double tf2 = tan(f2);
916 		double L1 = z * tf1 + (0.272488 / cos(f1));
917 		double L2 = z * tf2 - (0.272281 / cos(f2));
918 		double mu = gast - a / M_PI_180;
919 
920 		// Find Lat./Long. of center line on Earth's surface
921 		double cd = cos(d);
922 		double rho1 = sqrt(1 - 0.00669398 * cd * cd);
923 		// e^2 = 0.00669398 : Earth flattening parameter
924 		// IERS 2010 : f = 298.25642 : e^2 = 2f-f^2
925 		double y1 = y / rho1;
926 		double xi = x;
927 		double eta1 = y1;
928 		double sd = sin(d);
929 		double sd1 = sd / rho1;
930 		double cd1 = sqrt(1 - 0.00669398) * cd / rho1;
931 		double rho2 = sqrt(1 - 0.00669398 * sd * sd);
932 		double sd1d2 = 0.00669398 * sd * cd / (rho1 * rho2);
933 		double cd1d2 = sqrt(1 - sd1d2 * sd1d2);
934 		double lon = 0, mag = 0;
935 		double lat = 99; // initialize an impossible latitude to indicate no central eclipse
936 
937 		if ((1 - x * x - y1 * y1) > 0)
938 		{
939 			const double zeta1 = sqrt(1 - x * x - y1 * y1);
940 			const double zeta = rho2 * (zeta1 * cd1d2 - eta1 * sd1d2);
941 			//const double sd2 = sd * 1.0033641 / rho2;
942 			L2 = L2 - zeta * tf2;
943 			double b = -y * sd + zeta * cd;
944 			double theta = atan2(xi, b) / M_PI_180;
945 			if (theta < 0) theta += 360;
946 			if (mu > 360) mu -= 360;
947 			lon = mu - theta;
948 			if (lon < -180) lon += 360;
949 			if (lon > 180) lon -= 360;
950 			lon *= -1.0; // + East, - West
951 			double sfn1 = eta1 * cd1 + zeta1 * sd1;
952 			double cfn1 = sqrt(1 - sfn1 * sfn1);
953 			lat = 1.0033641 * sfn1 / cfn1;
954 			lat = atan(lat) / M_PI_180;
955 			L1 = L1 - zeta * tf1;
956 			// Magnitude of eclipse
957 			// mag < 1 = annular
958 			mag = L1 / (L1 + L2);
959 		}
960 		return Vec3d(lat, lon, mag);
961 	}
962 };
963 
getInfoStringExtra(const StelCore * core,const InfoStringGroup & flags) const964 QString Planet::getInfoStringExtra(const StelCore *core, const InfoStringGroup& flags) const
965 {
966 	QString str;
967 	QTextStream oss(&str);
968 
969 	if (flags&Extra)
970 	{
971 		const bool withTables = StelApp::getInstance().getFlagUseFormattingOutput();
972 		const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
973 		const double angularSize = 2.*getAngularSize(core)*M_PI_180;
974 		const double siderealPeriod = getSiderealPeriod(); // days required for revolution around parent.
975 		const double siderealDay = getSiderealDay(); // =re.period
976 		static SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
977 		PlanetP earth = ssystem->getEarth();
978 
979 #ifndef NDEBUG
980 		oss << QString("DEBUG: AberrationPush: %1/%2/%3 km<br/>")
981 			.arg(QString::number(AU * aberrationPush[0], 'f', 6))
982 			.arg(QString::number(AU * aberrationPush[1], 'f', 6))
983 			.arg(QString::number(AU * aberrationPush[2], 'f', 6));
984 
985 		Vec3d earthAberrationPush=earth->getAberrationPush();
986 		oss << QString("DEBUG: Earth's AberrationPush: %1/%2/%3 km<br/>")
987 			.arg(QString::number(AU * earthAberrationPush[0], 'f', 6))
988 			.arg(QString::number(AU * earthAberrationPush[1], 'f', 6))
989 			.arg(QString::number(AU * earthAberrationPush[2], 'f', 6));
990 
991 		PlanetP sun = ssystem->getSun();
992 		Vec3d sunAberrationPush=sun->getAberrationPush();
993 		oss << QString("DEBUG: Sun's AberrationPush: %1/%2/%3 km<br/>")
994 			.arg(QString::number(AU * sunAberrationPush[0], 'f', 6))
995 			.arg(QString::number(AU * sunAberrationPush[1], 'f', 6))
996 			.arg(QString::number(AU * sunAberrationPush[2], 'f', 6));
997 #endif
998 
999 		//PlanetP currentPlanet = core->getCurrentPlanet();
1000 		const bool onEarth = (core->getCurrentPlanet()==earth);
1001 		// TRANSLATORS: Unit of measure for speed - kilometers per second
1002 		QString kms = qc_("km/s", "speed");
1003 		// TRANSLATORS: Unit of measure for speed - meters per second
1004 		QString mps = qc_("m/s", "speed");
1005 
1006 		// This is a string you can activate for debugging. It shows the distance between observer and center of the body you are standing on.
1007 		// May be helpful for debugging critical parallax corrections for eclipses.
1008 		// For general use, find a better location first.
1009 		// oss << q_("Planetocentric distance &rho;: %1 (km)").arg(core->getCurrentObserver()->getDistanceFromCenter() * AU) <<"<br>";
1010 
1011 		// TRANSLATORS: Unit of measure for period - days
1012 		QString days = qc_("days", "duration");
1013 		if (siderealPeriod>0.0)
1014 		{
1015 			if (qAbs(siderealDay)>0 && getPlanetType()!=isArtificial)
1016 			{
1017 				const QByteArray fmt=withTables ? "<tr><td>%1: </td><td>%2</td></tr>" : "%1: %2<br/>";
1018 
1019 				if (withTables)
1020 					oss << "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
1021 				oss << QString(fmt).arg(q_("Sidereal day"), StelUtils::hoursToHmsStr(qAbs(siderealDay*24)));
1022 				if (englishName!="Sun")
1023 					oss << QString(fmt).arg(q_("Mean solar day"), StelUtils::hoursToHmsStr(qAbs(getMeanSolarDay()*24)));
1024 				if (withTables)
1025 					oss << "</table>";
1026 			}
1027 			else if (re.period==0.)
1028 			{
1029 				oss << q_("The period of rotation is chaotic") << "<br />";
1030 			}
1031 			if (qAbs(re.W1)>0.)
1032 			{
1033 				const double eqRotVel = (2.0*M_PI*AU/(360.*86400.0))*getEquatorialRadius()*re.W1;
1034 				if (eqRotVel>1.)
1035 					oss << QString("%1: %2 %3<br/>").arg(q_("Equatorial rotation velocity")).arg(qAbs(eqRotVel), 0, 'f', 3).arg(kms);
1036 				else
1037 					oss << QString("%1: %2 %3<br/>").arg(q_("Equatorial rotation velocity")).arg(qAbs(eqRotVel*1000.), 0, 'f', 3).arg(mps);
1038 
1039 			}
1040 			else if (qAbs(re.period)>0.)
1041 			{
1042 				const double eqRotVel = 2.0*M_PI*(AU*getEquatorialRadius())/(getSiderealDay()*86400.0);
1043 				if (eqRotVel>1.)
1044 					oss << QString("%1: %2 %3<br/>").arg(q_("Equatorial rotation velocity")).arg(qAbs(eqRotVel), 0, 'f', 3).arg(kms);
1045 				else
1046 					oss << QString("%1: %2 %3<br/>").arg(q_("Equatorial rotation velocity")).arg(qAbs(eqRotVel*1000.), 0, 'f', 3).arg(mps);
1047 			}
1048 		}
1049 
1050 		// PHYSICAL EPHEMERIS DATA
1051 		// Lunar phase names, libration, or axis orientation/rotation data
1052 		if (englishName=="Moon" && onEarth)
1053 		{
1054 			// For computing the Moon age we use geocentric coordinates
1055 			StelCore* core1 = StelApp::getInstance().getCore(); // we need non-const reference here.
1056 			const bool useTopocentric = core1->getUseTopocentricCoordinates();
1057 			core1->setUseTopocentricCoordinates(false);
1058 			core1->update(0); // enforce update cache!
1059 			const double eclJDE = earth->getRotObliquity(core1->getJDE());
1060 			double ra_equ, dec_equ, lambdaMoon, lambdaSun, betaMoon, betaSun, raSun, deSun;
1061 			StelUtils::rectToSphe(&ra_equ,&dec_equ, getEquinoxEquatorialPos(core1));
1062 			StelUtils::equToEcl(ra_equ, dec_equ, eclJDE, &lambdaMoon, &betaMoon);
1063 			StelUtils::rectToSphe(&raSun,&deSun, ssystem->getSun()->getEquinoxEquatorialPos(core1));
1064 			StelUtils::equToEcl(raSun, deSun, eclJDE, &lambdaSun, &betaSun);
1065 			core1->setUseTopocentricCoordinates(useTopocentric);
1066 			core1->update(0); // enforce update cache to avoid odd selection of Moon details!
1067 			const double deltaLong = StelUtils::fmodpos((lambdaMoon-lambdaSun)*M_180_PI, 360.);
1068 			QString moonPhase = "";
1069 			if (deltaLong<0.5 || deltaLong>359.5)
1070 				moonPhase = qc_("New Moon", "Moon phase");
1071 			else if (deltaLong<89.5)
1072 				moonPhase = qc_("Waxing Crescent", "Moon phase");
1073 			else if (deltaLong<90.5)
1074 				moonPhase = qc_("First Quarter", "Moon phase");
1075 			else if (deltaLong<179.5)
1076 				moonPhase = qc_("Waxing Gibbous", "Moon phase");
1077 			else if (deltaLong<180.5)
1078 				moonPhase = qc_("Full Moon", "Moon phase");
1079 			else if (deltaLong<269.5)
1080 				moonPhase = qc_("Waning Gibbous", "Moon phase");
1081 			else if (deltaLong<270.5)
1082 				moonPhase = qc_("Third Quarter", "Moon phase");
1083 			else if (deltaLong<359.5)
1084 				moonPhase = qc_("Waning Crescent", "Moon phase");
1085 			else
1086 			{
1087 				qWarning() << "ERROR IN PHASE STRING PROGRAMMING!";
1088 				Q_ASSERT(0);
1089 			}
1090 
1091 			const double age = deltaLong*29.530588853/360.;
1092 			oss << QString("%1: %2 %3").arg(q_("Moon age"), QString::number(age, 'f', 1), q_("days old"));
1093 			if (!moonPhase.isEmpty())
1094 				oss << QString(" (%4)").arg(moonPhase);
1095 			oss << "<br />";
1096 
1097 			if (useTopocentric)
1098 			{
1099 				// we must repeat the position lookup from above in case we have topocentric corrections.
1100 				StelUtils::rectToSphe(&ra_equ,&dec_equ, getEquinoxEquatorialPos(core));
1101 				StelUtils::rectToSphe(&raSun,&deSun, ssystem->getSun()->getEquinoxEquatorialPos(core));
1102 			}
1103 			const double chi=atan2(cos(deSun)*sin(raSun-ra_equ), sin(deSun)*cos(dec_equ)-cos(deSun)*sin(dec_equ)*cos(raSun-ra_equ));
1104 			QString chiStr;
1105 			if (withDecimalDegree)
1106 				chiStr=StelUtils::radToDecDegStr(StelUtils::fmodpos(chi, M_PI*2.0), 1);
1107 			else
1108 				chiStr=StelUtils::radToDmsStr(chi, false);
1109 			if (withTables)
1110 			{
1111 				oss << "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
1112 				oss << QString("<tr><td colspan=\"2\">%1:</td><td align=\"right\"> %2</td></tr>").arg(q_("Position angle of bright limb"), chiStr);
1113 			}
1114 			else
1115 				oss << QString("%1: %2<br/>").arg(q_("Position angle of bright limb"), chiStr);
1116 
1117 			// Everything around libration
1118 			const QStringList compassDirs={
1119 				qc_("S",   "compass direction"),
1120 				qc_("SSW", "compass direction"),
1121 				qc_("SW",  "compass direction"),
1122 				qc_("WSW", "compass direction"),
1123 				qc_("W",   "compass direction"),
1124 				qc_("WNW", "compass direction"),
1125 				qc_("NW",  "compass direction"),
1126 				qc_("NNW", "compass direction"),
1127 				qc_("N",   "compass direction"),
1128 				qc_("NNE", "compass direction"),
1129 				qc_("NE",  "compass direction"),
1130 				qc_("ENE", "compass direction"),
1131 				qc_("E",   "compass direction"),
1132 				qc_("ESE", "compass direction"),
1133 				qc_("SE",  "compass direction"),
1134 				qc_("SSE", "compass direction")};
1135 
1136 			QPair<Vec4d, Vec3d> ssop=getSubSolarObserverPoints(core);
1137 
1138 			const double Be=ssop.first[1];
1139 			double Le   =StelUtils::fmodpos(-ssop.first[2],  M_PI*2.0); if (Le>M_PI) Le-=2.0*M_PI;
1140 			const double Bs=ssop.second[1];
1141 			double Ls   =StelUtils::fmodpos(-ssop.second[2], M_PI*2.0); if (Ls>M_PI) Ls-=2.0*M_PI;
1142 			const double totalLibr=sqrt(Le*Le+Be*Be);
1143 			double librAngle=StelUtils::fmodpos(atan2(Le, -Be), 2.0*M_PI);
1144 			// find out and indicate which limb is optimally visible
1145 			const int limbSector= std::lround(floor(StelUtils::fmodpos(librAngle*M_180_PI+11.25, 360.)/22.5));
1146 			QString limbStr=compassDirs.at(limbSector);
1147 			if (totalLibr>3.*M_PI_180)
1148 				limbStr.append("!");
1149 			if (totalLibr>5.*M_PI_180)
1150 				limbStr.append("!");
1151 			if (totalLibr>7.*M_PI_180)
1152 				limbStr.append("!");
1153 			QString paAxisStr, libLStr, libBStr, subsolarLStr, subsolarBStr, colongitudeStr, totalLibrationStr, librationAngleStr;
1154 			if (withDecimalDegree)
1155 			{
1156 				paAxisStr=StelUtils::radToDecDegStr(ssop.first[3], 1);
1157 				libLStr=StelUtils::radToDecDegStr(Le, 1);
1158 				libBStr=StelUtils::radToDecDegStr(Be, 1);
1159 				subsolarLStr=StelUtils::radToDecDegStr(Ls, 1);
1160 				subsolarBStr=StelUtils::radToDecDegStr(Bs, 1);
1161 				colongitudeStr=StelUtils::radToDecDegStr(StelUtils::fmodpos(450.0*M_PI_180-Ls, M_PI*2.0), 1);
1162 				totalLibrationStr=StelUtils::radToDecDegStr(totalLibr, 1);
1163 				librationAngleStr=StelUtils::radToDecDegStr(librAngle, 1);
1164 			}
1165 			else
1166 			{
1167 				paAxisStr=StelUtils::radToDmsStr(ssop.first[3]);
1168 				libLStr=StelUtils::radToDmsStr(Le);
1169 				libBStr=StelUtils::radToDmsStr(Be);
1170 				subsolarLStr=StelUtils::radToDmsStr(Ls);
1171 				subsolarBStr=StelUtils::radToDmsStr(Bs);
1172 				colongitudeStr=StelUtils::radToDmsStr(StelUtils::fmodpos(450.0*M_PI_180-Ls, M_PI*2.0));
1173 				totalLibrationStr=StelUtils::radToDmsStr(totalLibr);
1174 				librationAngleStr=StelUtils::radToDmsStr(librAngle);
1175 			}
1176 			if (withTables)
1177 			{
1178 				//oss << "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
1179 				oss << QString("<tr><td colspan=\"2\">%1:</td><td align=\"right\"> %2</td></tr>").arg(q_("Position Angle of axis"), paAxisStr);
1180 				oss << QString("<tr><td>%1:</td><td align=\"right\">%2 %3</td><td align=\"right\"> %4</td><td>(%5)</td></tr>").arg(q_("Libration"), totalLibrationStr, qc_("towards", "into the direction of"), librationAngleStr, limbStr);
1181 				oss << QString("<tr><td>%1:</td><td align=\"right\">L: %2</td><td align=\"right\">B: %3</td></tr>").arg(q_("Libration"), libLStr, libBStr);
1182 				oss << QString("<tr><td>%1:</td><td align=\"right\">L<sub>s</sub>: %2</td><td align=\"right\">B<sub>s</sub>: %3</td></tr>").arg(q_("Subsolar point"), subsolarLStr, subsolarBStr);
1183 				oss << QString("<tr><td>%1:</td><td align=\"right\">c<sub>0</sub>: %2</td></tr>").arg(q_("Colongitude"), colongitudeStr);
1184 				oss << "</table>";
1185 			}
1186 			else
1187 			{
1188 				oss << QString("%1: %2<br/>").arg(q_("Position Angle of axis"), paAxisStr);
1189 				oss << QString("%1: %2 %3 %4 (%5)<br/>").arg(q_("Libration"), totalLibrationStr, qc_("towards", "into the direction of"), librationAngleStr, limbStr);
1190 				oss << QString("%1: %2/%3<br/>").arg(q_("Libration"), libLStr, libBStr);
1191 				oss << QString("%1: %2/%3<br/>").arg(q_("Subsolar point"), subsolarLStr, subsolarBStr);
1192 				oss << QString("%1: %2<br/>").arg(q_("Colongitude"), colongitudeStr);
1193 			}
1194 		}
1195 		else if (englishName!="Sun" && onEarth)
1196 		{
1197 			// The planetographic longitudes (central meridian etc) are counted in the other direction than on Moon.
1198 			QPair<Vec4d, Vec3d> ssop=getSubSolarObserverPoints(core);
1199 
1200 			const double Le=StelUtils::fmodpos(ssop.first[2],  M_PI*2.0);
1201 			const double Ls=StelUtils::fmodpos(ssop.second[2], M_PI*2.0);
1202 
1203 			QString paAxisStr, subearthLStr, subearthBStr, subsolarLStr, subsolarBStr;
1204 			const QString lngSystem=(englishName=="Jupiter" ? "II" : (englishName=="Saturn" ? "III" : ""));
1205 			if (withDecimalDegree)
1206 			{
1207 				paAxisStr=StelUtils::radToDecDegStr(ssop.first[3], 1);
1208 				subearthLStr=StelUtils::radToDecDegStr(Le, 1);
1209 				subearthBStr=StelUtils::radToDecDegStr(ssop.first[1], 1);
1210 				subsolarLStr=StelUtils::radToDecDegStr(Ls, 1);
1211 				subsolarBStr=StelUtils::radToDecDegStr(ssop.second[1], 1);
1212 			}
1213 			else
1214 			{
1215 				paAxisStr=StelUtils::radToDmsStr(ssop.first[3]);
1216 				subearthLStr=StelUtils::radToDmsStr(Le);
1217 				subearthBStr=StelUtils::radToDmsStr(ssop.first[1]);
1218 				subsolarLStr=StelUtils::radToDmsStr(Ls);
1219 				subsolarBStr=StelUtils::radToDmsStr(ssop.second[1]);
1220 			}
1221 			if (withTables)
1222 			{
1223 				oss << "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
1224 				oss << QString("<tr><td colspan=\"2\">%1:</td><td align=\"right\"> %2</td></tr>").arg(q_("Position Angle of axis"), paAxisStr);
1225 				oss << QString("<tr><td>%1:</td><td align=\"right\">L<sub>%2e</sub>: %3</td><td align=\"right\">&phi;<sub>e</sub>: %4</td></tr>").arg(q_("Center point"),   lngSystem, subearthLStr, subearthBStr);
1226 				oss << QString("<tr><td>%1:</td><td align=\"right\">L<sub>%2s</sub>: %3</td><td align=\"right\">&phi;<sub>s</sub>: %4</td></tr>").arg(q_("Subsolar point"), lngSystem, subsolarLStr, subsolarBStr);
1227 				oss << "</table>";
1228 			}
1229 			else
1230 			{
1231 				oss << QString("%1: %2<br/>").arg(q_("Position Angle of axis"), paAxisStr);
1232 				oss << QString("%1: L<sub>%2e</sub>=%3 &phi;<sub>e</sub>: %4<br/>").arg(q_("Center point"),   lngSystem, subearthLStr, subearthBStr);
1233 				oss << QString("%1: L<sub>%2s</sub>=%3 &phi;<sub>s</sub>: %4<br/>").arg(q_("Subsolar point"), lngSystem, subsolarLStr, subsolarBStr);
1234 			}
1235 		}
1236 
1237 		if (englishName=="Sun")
1238 		{
1239 			// Only show during eclipse or transit, show percent?
1240 			QPair<double, PlanetP> eclObj = ssystem->getSolarEclipseFactor(core);
1241 			const double eclipseObscuration = 100.*(1.-eclObj.first);
1242 			if (eclipseObscuration>1.e-7) // needed to avoid false display of 1e-14 or so.
1243 			{
1244 				oss << QString("%1: %2%<br />").arg(q_("Eclipse obscuration")).arg(QString::number(eclipseObscuration, 'f', 2));
1245 				PlanetP obj = eclObj.second;
1246 				if (onEarth && obj == ssystem->getMoon())
1247 				{
1248 					const double eclipseMagnitude =
1249 							(0.5 * angularSize
1250 							 + (obj->getAngularSize(core) * M_PI_180) / obj->getInfoMap(core)["scale"].toDouble()
1251 							- getJ2000EquatorialPos(core).angle(obj->getJ2000EquatorialPos(core)))
1252 							/ angularSize;
1253 					oss << QString("%1: %2<br />").arg(q_("Eclipse magnitude")).arg(QString::number(eclipseMagnitude, 'f', 3));
1254 				}
1255 			}
1256 
1257 			if (onEarth)
1258 			{
1259 				// Solar eclipse information
1260 				// Use geocentric coordinates
1261 				StelCore* core1 = StelApp::getInstance().getCore();
1262 				const bool useTopocentric = core1->getUseTopocentricCoordinates();
1263 				core1->setUseTopocentricCoordinates(false);
1264 				core1->update(0);
1265 
1266 				double raSun, deSun, raMoon, deMoon;
1267 				StelUtils::rectToSphe(&raSun, &deSun, ssystem->getSun()->getEquinoxEquatorialPos(core1));
1268 				StelUtils::rectToSphe(&raMoon, &deMoon, ssystem->getMoon()->getEquinoxEquatorialPos(core1));
1269 
1270 				double raDiff = StelUtils::fmodpos((raMoon - raSun)/M_PI_180, 360.0);
1271 				if (raDiff < 3. || raDiff > 357.)
1272 				{
1273 					SolarEclipse solarEclipse;
1274 					Vec3d pos = solarEclipse.point(core1);
1275 
1276 					if (pos[0] < 90.) // only display when shadow axis is touching Earth
1277 					{
1278 						QString info = q_("Center of solar eclipse (Lat./Long.)");
1279 						if (withDecimalDegree)
1280 							oss << QString("%1: %2%3/%4%5<br />").arg(info).arg(pos[0], 5, 'f', 4).arg(QChar(0x00B0)).arg(pos[1], 5, 'f', 4).arg(QChar(0x00B0));
1281 						else
1282 							oss << QString("%1: %2/%3<br />").arg(info).arg(StelUtils::decDegToDmsStr(pos[0])).arg(StelUtils::decDegToDmsStr(pos[1]));
1283 						StelLocation loc = core->getCurrentLocation();
1284 						// distance between center point and current location
1285 						double distance = loc.distanceKm(pos[1], pos[0]);
1286 						double azimuth = loc.getAzimuthForLocation(pos[1], pos[0]);
1287 
1288 						oss << QString("%1 %2 %3 %4%5<br/>")
1289 								 .arg(q_("Shadow center point is"))
1290 								 .arg(QString::number(distance, 'f', 1))
1291 								 .arg(q_("km towards azimuth"))
1292 								 .arg(QString::number(azimuth, 'f', 1))
1293 								 .arg(QChar(0x00B0));
1294 						oss << QString("%1: %2 ")
1295 								 .arg(q_("Magnitude of central eclipse"))
1296 								 .arg(QString::number(pos[2], 'f', 3));
1297 						if (pos[2] < 1.0)
1298 							oss << QString(qc_("(annular)","type of solar eclipse"));
1299 						else
1300 							oss << QString(qc_("(total)","type of solar eclipse"));
1301 						oss << "<br/>";
1302 					}
1303 				}
1304 				core1->setUseTopocentricCoordinates(useTopocentric);
1305 				core1->update(0); // enforce update cache to avoid odd selection of Moon details!
1306 			}
1307 		}
1308 
1309 		if (englishName == "Moon" && onEarth)
1310 		{
1311 			// Show magnitude of lunar eclipse
1312 			QPair<double,double> magnitudes = getLunarEclipseMagnitudes();
1313 			if (magnitudes.first > 1.e-3)
1314 			{
1315 				oss << QString("%1: %2%<br/>").arg(q_("Penumbral eclipse magnitude"), QString::number(magnitudes.first*100., 'f', 1));
1316 				if (magnitudes.second > 1.e-3)
1317 				{
1318 					oss << QString("%1: %2%<br/>").arg(q_("Umbral eclipse magnitude"), QString::number(magnitudes.second*100., 'f', 1));
1319 				}
1320 			}
1321 		}
1322 
1323 		// Not sure if albedo is at all interesting?
1324 		if (englishName != "Sun")
1325 			oss << QString("%1: %2<br/>").arg(q_("Albedo"), QString::number(getAlbedo(), 'f', 2));
1326 	}
1327 	return str;
1328 }
1329 
getInfoMap(const StelCore * core) const1330 QVariantMap Planet::getInfoMap(const StelCore *core) const
1331 {
1332 	static SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
1333 	PlanetP earth = ssystem->getEarth();
1334 	const bool onEarth = (core->getCurrentPlanet()==earth);
1335 	QVariantMap map = StelObject::getInfoMap(core);
1336 
1337 	if (getEnglishName()!="Sun")
1338 	{
1339 		const Vec3d& observerHelioPos = core->getObserverHeliocentricEclipticPos();
1340 		map.insert("distance", getJ2000EquatorialPos(core).length());
1341 		float phase=getPhase(observerHelioPos);
1342 		map.insert("phase", phase);
1343 		map.insert("illumination", 100.f*phase);
1344 		double phaseAngle = getPhaseAngle(observerHelioPos);
1345 		map.insert("phase-angle", phaseAngle);
1346 		map.insert("phase-angle-dms", StelUtils::radToDmsStr(phaseAngle));
1347 		map.insert("phase-angle-deg", StelUtils::radToDecDegStr(phaseAngle));
1348 		double elongation = getElongation(observerHelioPos);
1349 		map.insert("elongation", elongation);
1350 		map.insert("elongation-dms", StelUtils::radToDmsStr(elongation));
1351 		map.insert("elongation-deg", StelUtils::radToDecDegStr(elongation));
1352 		map.insert("velocity", getEclipticVelocity().toString());
1353 		map.insert("velocity-kms", QString::number(getEclipticVelocity().length()* AU/86400., 'f', 5));
1354 		map.insert("heliocentric-velocity", getHeliocentricEclipticVelocity().toString());
1355 		map.insert("heliocentric-velocity-kms", QString::number(getHeliocentricEclipticVelocity().length()* AU/86400., 'f', 5));
1356 		map.insert("scale", sphereScale);
1357 		map.insert("albedo", getAlbedo());
1358 	}
1359 	else
1360 	{
1361 		SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
1362 		QPair<double, PlanetP> eclObj = ssystem->getSolarEclipseFactor(core);
1363 		const double eclipseObscuration = 100.*(1.-eclObj.first);
1364 		if (eclipseObscuration>1.e-7)
1365 		{
1366 			map.insert("eclipse-obscuration", eclipseObscuration);
1367 			PlanetP obj = eclObj.second;
1368 			if (core->getCurrentPlanet()==ssystem->getEarth() && obj==ssystem->getMoon())
1369 			{
1370 				double angularSize = 2.*getAngularSize(core)*M_PI_180;
1371 				const double eclipseMagnitude = (0.5*angularSize + (obj->getAngularSize(core)*M_PI_180)/obj->getInfoMap(core)["scale"].toDouble() - getJ2000EquatorialPos(core).angle(obj->getJ2000EquatorialPos(core)))/angularSize;
1372 				map.insert("eclipse-magnitude", eclipseMagnitude);
1373 			}
1374 			else
1375 				map.insert("eclipse-magnitude", 0.0);
1376 		}
1377 		else
1378 		{
1379 			map.insert("eclipse-obscuration", 0.0);
1380 			map.insert("eclipse-magnitude", 0.0);
1381 		}
1382 	}
1383 	map.insert("type", getPlanetTypeString()); // replace existing "type=Planet" by something more detailed.
1384 
1385 	if (onEarth && (getEnglishName()=="Moon"))
1386 	{
1387 		// Everything around libration:
1388 		QPair<Vec4d, Vec3d>phys=getSubSolarObserverPoints(core);
1389 		map.insert("libration_l", -phys.first[2]*M_180_PI); // longitude counted the other way!
1390 		map.insert("libration_b", phys.first[1]*M_180_PI);
1391 		map.insert("pa_axis", phys.first[3]*M_180_PI);
1392 		map.insert("subsolar_l", -phys.second[2]*M_180_PI);
1393 		map.insert("subsolar_b", phys.second[1]*M_180_PI);
1394 		map.insert("colongitude", StelUtils::fmodpos(450.0+phys.second[2]*M_PI_180, 360.));
1395 
1396 		QPair<double,double> magnitudes = getLunarEclipseMagnitudes();
1397 		map.insert("penumbral-eclipse-magnitude", magnitudes.first);
1398 		map.insert("umbral-eclipse-magnitude", magnitudes.second);
1399 	}
1400 	else if (onEarth && (getEnglishName()!="Sun"))
1401 	{
1402 		QPair<Vec4d, Vec3d>phys=getSubSolarObserverPoints(core);
1403 		map.insert("central_l", phys.first[2]*M_180_PI);
1404 		map.insert("central_b", phys.first[1]*M_180_PI);
1405 		map.insert("pa_axis", phys.first[3]*M_180_PI);
1406 		map.insert("subsolar_l", phys.second[2]*M_180_PI);
1407 		map.insert("subsolar_b", phys.second[1]*M_180_PI);
1408 	}
1409 	return map;
1410 }
1411 
getLunarEclipseMagnitudes() const1412 QPair<double,double> Planet::getLunarEclipseMagnitudes() const
1413 {
1414 	QPair<double,double> magnitudes;
1415 	// Use geocentric coordinates
1416 	StelCore* core = StelApp::getInstance().getCore();
1417 	static SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
1418 	const bool saveTopocentric = core->getUseTopocentricCoordinates();
1419 	core->setUseTopocentricCoordinates(false);
1420 	core->update(0);
1421 
1422 	double raMoon, deMoon, raSun, deSun;
1423 	StelUtils::rectToSphe(&raMoon, &deMoon, getEquinoxEquatorialPos(core));
1424 	StelUtils::rectToSphe(&raSun, &deSun, ssystem->getSun()->getEquinoxEquatorialPos(core));
1425 
1426 	// R.A./Dec of Earth's shadow
1427 	const double raShadow = StelUtils::fmodpos(raSun + M_PI, 2.*M_PI);
1428 	const double deShadow = -(deSun);
1429 	const double raDiff = StelUtils::fmodpos(raMoon - raShadow, 2.*M_PI);
1430 
1431 	if (raDiff < 3.*M_PI_180 || raDiff > 357.*M_PI_180)
1432 	{
1433 		// Moon's semi-diameter
1434 		const double mSD=atan(getEquatorialRadius()/eclipticPos.length()) * M_180_PI*3600.; // arcsec
1435 		const QPair<Vec3d,Vec3d>shadowRadii=ssystem->getEarthShadowRadiiAtLunarDistance();
1436 		const double f1 = shadowRadii.second[0]; // radius of penumbra at the distance of the Moon
1437 		const double f2 = shadowRadii.first[0];  // radius of umbra at the distance of the Moon
1438 
1439 		double x = cos(deMoon) * sin(raDiff);
1440 		x *= 3600. * M_180_PI;
1441 		double y = cos(deShadow) * sin(deMoon) - sin(deShadow) * cos(deMoon) * cos(raDiff);
1442 		y *= 3600. * M_180_PI;
1443 		const double m = sqrt(x * x + y * y); // distance between lunar centre and shadow centre
1444 		const double L1 = f1 + mSD; // distance between center of the Moon and shadow at beginning and end of penumbral eclipse
1445 		const double L2 = f2 + mSD; // distance between center of the Moon and shadow at beginning and end of partial eclipse
1446 		const double pMag = (L1 - m) / (2. * mSD); // penumbral magnitude
1447 		const double uMag = (L2 - m) / (2. * mSD); // umbral magnitude
1448 
1449 		magnitudes.first = pMag;
1450 		magnitudes.second = uMag;
1451 	}
1452 	else
1453 	{
1454 		magnitudes.first = 0.;
1455 		magnitudes.second = 0.;
1456 	}
1457 	core->setUseTopocentricCoordinates(saveTopocentric);
1458 	core->update(0); // enforce update cache to avoid odd selection of Moon details!
1459 	return magnitudes;
1460 }
1461 
getSelectPriority(const StelCore * core) const1462 float Planet::getSelectPriority(const StelCore* core) const
1463 {
1464 	if( (static_cast<SolarSystem*>(StelApp::getInstance().getModuleMgr().getModule("SolarSystem")))->getFlagHints() )
1465 	{
1466 		// easy to select, especially pluto
1467 		return getVMagnitudeWithExtinction(core)-15.f;
1468 	}
1469 	else
1470 	{
1471 		return getVMagnitudeWithExtinction(core) - 8.f;
1472 	}
1473 }
1474 
getInfoColor(void) const1475 Vec3f Planet::getInfoColor(void) const
1476 {
1477 	return (static_cast<SolarSystem*>(StelApp::getInstance().getModuleMgr().getModule("SolarSystem")))->getLabelsColor();
1478 }
1479 
1480 
getCloseViewFov(const StelCore * core) const1481 double Planet::getCloseViewFov(const StelCore* core) const
1482 {
1483 	return std::atan(equatorialRadius*sphereScale*2./getEquinoxEquatorialPos(core).length())*M_180_PI * 4.;
1484 }
1485 
getSatellitesFov(const StelCore * core) const1486 double Planet::getSatellitesFov(const StelCore* core) const
1487 {
1488 	// TODO: calculate from satellite orbits rather than hard code
1489 	if (englishName=="Jupiter") return std::atan(0.005 /getEquinoxEquatorialPos(core).length())*M_180_PI * 4.;
1490 	if (englishName=="Saturn")  return std::atan(0.005 /getEquinoxEquatorialPos(core).length())*M_180_PI * 4.;
1491 	if (englishName=="Mars")    return std::atan(0.0001/getEquinoxEquatorialPos(core).length())*M_180_PI * 4.;
1492 	if (englishName=="Uranus")  return std::atan(0.002 /getEquinoxEquatorialPos(core).length())*M_180_PI * 4.;
1493 	return -1.;
1494 }
1495 
getParentSatellitesFov(const StelCore * core) const1496 double Planet::getParentSatellitesFov(const StelCore* core) const
1497 {
1498 	if (parent && parent->parent) return parent->getSatellitesFov(core);
1499 	return -1.0;
1500 }
1501 
1502 // Set the rotational elements of the planet body.
setRotationElements(const QString name,const double _period,const double _offset,const double _epoch,const double _obliquity,const double _ascendingNode,const double _ra0,const double _ra1,const double _de0,const double _de1,const double _w0,const double _w1)1503 void Planet::setRotationElements(const QString name,
1504 				 const double _period, const double _offset, const double _epoch, const double _obliquity, const double _ascendingNode,
1505 				 const double _ra0, const double _ra1, const double _de0, const double _de1, const double _w0, const double _w1)
1506 {
1507 	re.period = _period;
1508 	re.offset = _offset;
1509 	re.epoch = _epoch;
1510 	re.obliquity = _obliquity;
1511 	re.ascendingNode = _ascendingNode;
1512 	re.method=(_ra0==0. ? RotationElements::Traditional : RotationElements::WGCCRE);
1513 	re.ra0=_ra0;
1514 	re.ra1=_ra1;
1515 	re.de0=_de0;
1516 	re.de1=_de1;
1517 	re.W0=_w0;
1518 	re.W1=_w1;
1519 
1520 	// Assign fine-tuning corrective functions for axis rotation angle W and orientation.
1521 	re.corrW  =RotationElements::axisRotCorrFuncMap.value(name, &RotationElements::corrWnil);
1522 	re.corrOri=RotationElements::axisOriCorrFuncMap.value(name, &RotationElements::corrOriNil);
1523 }
1524 
setSiderealPeriod(const double siderealPeriod)1525 void Planet::setSiderealPeriod(const double siderealPeriod)
1526 {
1527 	Q_ASSERT(!qFuzzyCompare(siderealPeriod, 0.) || (orbitPtr && pType!=isObserver) || englishName=="Sun");
1528 
1529 	this->siderealPeriod = siderealPeriod;
1530 	if (orbitPtr && pType!=isObserver)
1531 	{
1532 		const double semiMajorAxis=static_cast<KeplerOrbit*>(orbitPtr)->getSemimajorAxis();
1533 		const double eccentricity=static_cast<KeplerOrbit*>(orbitPtr)->getEccentricity();
1534 		if (semiMajorAxis>0 && eccentricity<0.9)
1535 		{
1536 			//qDebug() << "Planet " << englishName << "replace siderealPeriod " << re.siderealPeriod << "by";
1537 			this->siderealPeriod=static_cast<KeplerOrbit*>(orbitPtr)->calculateSiderealPeriod();
1538 			//qDebug() << re.siderealPeriod;
1539 			closeOrbit=true;
1540 		}
1541 		else
1542 			closeOrbit=false;
1543 	}
1544 	deltaOrbitJDE = siderealPeriod/ORBIT_SEGMENTS;
1545 }
1546 
1547 // A Planet's own eclipticPos is in VSOP87 ref. frame (practically equal to ecliptic of J2000 for us) coordinates relative to the parent body (sun, planet).
1548 // To get J2000 equatorial coordinates, we require heliocentric ecliptical positions (adding up parent positions) of observer and Planet.
1549 // Then we use the matrix rotation multiplication with an existing matrix in StelCore to orient from eclipticalJ2000 to equatorialJ2000.
1550 // The end result is a non-normalized 3D vector which allows retrieving distances etc.
1551 // To apply aberration correction, we need the velocity vector of the observer's planet and apply a little correction in SolarSystem::computePositions()
1552 // prepare for aberration: Explan. Suppl. 2013, (7.38)
getJ2000EquatorialPos(const StelCore * core) const1553 Vec3d Planet::getJ2000EquatorialPos(const StelCore *core) const
1554 {
1555 	const bool withAberration=core->getUseAberration();
1556 	return StelCore::matVsop87ToJ2000.multiplyWithoutTranslation(getHeliocentricEclipticPos()
1557 								    - core->getObserverHeliocentricEclipticPos()
1558 								    + (withAberration ? aberrationPush : Vec3d(0.)));
1559 }
1560 
1561 // return value in radians!
1562 // For Earth, this is epsilon_A, the angle between earth's rotational axis and pole of mean ecliptic of date.
1563 // Details: e.g. Hilton etal, Report on Precession and the Ecliptic, Cel.Mech.Dyn.Astr.94:351-67 (2006), Fig1.
1564 // For the other planets, it must be the angle between axis and Normal to the VSOP_J2000 coordinate frame.
1565 // For moons, it may be the obliquity against its planet's equatorial plane.
1566 // GZ: Note that such a scheme is highly confusing, and should be avoided. IAU models use the J2000 ICRF frame.
1567 // TODO: It is unclear what other planets should deliver here.
1568 //     In any case, re.obliquity could now be updated during computeTransMatrix()
getRotObliquity(double JDE) const1569 double Planet::getRotObliquity(double JDE) const
1570 {
1571 	// JDE=2451545.0 for J2000.0
1572 	if (englishName=="Earth")
1573 		return getPrecessionAngleVondrakEpsilon(JDE);
1574 	else
1575 		return static_cast<double>(re.obliquity);
1576 }
1577 
1578 // Find out if p casts a shadow onto thisPlanet
willCastShadow(const Planet * thisPlanet,const Planet * p,const Planet * sun)1579 static bool willCastShadow(const Planet* thisPlanet, const Planet* p, const Planet* sun)
1580 {
1581 	Q_UNUSED(sun)
1582 	const Vec3d thisPos = thisPlanet->getHeliocentricEclipticPos();
1583 	const Vec3d planetPos = p->getHeliocentricEclipticPos();
1584 
1585 	// If the planet p is farther from the sun than this planet, it can't cast shadow on it.
1586 	if (planetPos.lengthSquared()>thisPos.lengthSquared())
1587 		return false;
1588 
1589 	// Very tentative solution
1590 	Vec3d ppVector = planetPos; //-sun->getAberrationPush();
1591 	ppVector.normalize();
1592 
1593 	double shadowDistance = ppVector * thisPos;
1594 	static const double sunRadius = 696000./AU;
1595 	const double d = planetPos.length() / (p->getEquatorialRadius()/sunRadius+1);
1596 	double penumbraRadius = (shadowDistance-d)/d*sunRadius;
1597 	// TODO: Note that Earth's shadow should be enlarged a bit. (6-7% following Danjon?)
1598 
1599 	double penumbraCenterToThisPlanetCenterDistance = (ppVector*shadowDistance-thisPos).length();
1600 
1601 	if (penumbraCenterToThisPlanetCenterDistance<penumbraRadius+thisPlanet->getEquatorialRadius())
1602 		return true;
1603 	return false;
1604 }
1605 
getCandidatesForShadow() const1606 QVector<const Planet*> Planet::getCandidatesForShadow() const
1607 {
1608 	QVector<const Planet*> res;
1609 	const SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
1610 	const Planet* sun = ssystem->getSun().data();
1611 	if (this==sun || (parent.data()==sun && satellites.empty()))
1612 		return res;
1613 
1614 	for (const auto& planet : satellites)
1615 	{
1616 		if (willCastShadow(this, planet.data(), sun))
1617 			res.append(planet.data());
1618 	}
1619 	if (willCastShadow(this, parent.data(), sun))
1620 		res.append(parent.data());
1621 	// Test satellites mutual occultations.
1622 	if (parent.data() != sun)
1623 	{
1624 		for (const auto& planet : parent->satellites)
1625 		{
1626 			//skip self-shadowing
1627 			if(planet.data() == this )
1628 				continue;
1629 			if (willCastShadow(this, planet.data(), sun))
1630 				res.append(planet.data());
1631 		}
1632 	}
1633 
1634 	return res;
1635 }
1636 
computePosition(const double dateJDE,const Vec3d & aberrationPush)1637 void Planet::computePosition(const double dateJDE, const Vec3d &aberrationPush)
1638 {
1639 	if (fabs(lastJDE-dateJDE)>deltaJDE)
1640 	{
1641 		coordFunc(dateJDE, eclipticPos, eclipticVelocity, orbitPtr);
1642 		lastJDE = dateJDE;
1643 	}
1644 	this->aberrationPush=aberrationPush;
1645 }
1646 
computePosition(const double dateJDE,Vec3d & eclPosition,Vec3d & eclVelocity) const1647 void Planet::computePosition(const double dateJDE, Vec3d &eclPosition, Vec3d &eclVelocity) const
1648 {
1649 		coordFunc(dateJDE, eclPosition, eclVelocity, orbitPtr);
1650 }
1651 
1652 // Compute the transformation matrix from the local Planet coordinate system to the parent Planet coordinate system.
1653 // In case of the planets, this makes the axis point to their respective celestial poles.
1654 // If only old-style rotational elements exist, we use the original algorithm (as of ~2010).
computeTransMatrix(double JD,double JDE)1655 void Planet::computeTransMatrix(double JD, double JDE)
1656 {
1657 	//QString debugAid; // We have to collect all debug strings to keep some order in the output.
1658 
1659 	// We have to call with both to correct this for earth with the new model.
1660 	// For Earth, this is sidereal time for Greenwich, i.e. hour angle between meridian and First Point of Aries.
1661 	// OLD: Return angle between ascending node of planet's equator and (J2000) ecliptic (?)
1662 	// NEW: For the planets, this should return angle W between ascending node of the planet's equator with ICRF equator and the planet's zero meridian.
1663 	re.currentAxisW=getSiderealTime(JD, JDE); // Store to later compute central meridian data etc.
1664 	axisRotation = static_cast<float>(re.currentAxisW);
1665 
1666 	// We can inject a proper precession plus even nutation matrix in this stage, if available.
1667 	if (englishName=="Earth")
1668 	{
1669 		// rotLocalToParent = Mat4d::zrotation(re.ascendingNode - re.precessionRate*(jd-re.epoch)) * Mat4d::xrotation(-getRotObliquity(jd));
1670 		// We follow Capitaine's (2003) formulation P=Rz(Chi_A)*Rx(-omega_A)*Rz(-psi_A)*Rx(eps_o). (Explan.Suppl. 2013, 6.28)
1671 		// ADS: 2011A&A...534A..22V = A&A 534, A22 (2011): Vondrak, Capitane, Wallace: New Precession Expressions, valid for long time intervals:
1672 		// See also Hilton et al., Report on Precession and the Ecliptic. Cel.Mech.Dyn.Astr. 94:351-367 (2006), eqn (6) and (21).
1673 		double eps_A, chi_A, omega_A, psi_A;
1674 		getPrecessionAnglesVondrak(JDE, &eps_A, &chi_A, &omega_A, &psi_A);
1675 		// Canonical precession rotations: Nodal rotation psi_A,
1676 		// then rotation by omega_A, the angle between EclPoleJ2000 and EarthPoleOfDate.
1677 		// The final rotation by chi_A rotates the equinox (zero degree).
1678 		// To achieve ecliptical coords of date, you just have now to add a rotX by epsilon_A (obliquity of date).
1679 
1680 		rotLocalToParent= Mat4d::zrotation(-psi_A) * Mat4d::xrotation(-omega_A) * Mat4d::zrotation(chi_A);
1681 		// Plus nutation IAU-2000B:
1682 		if (StelApp::getInstance().getCore()->getUseNutation())
1683 		{
1684 			double deltaEps, deltaPsi;
1685 			getNutationAngles(JDE, &deltaPsi, &deltaEps);
1686 			//qDebug() << "deltaEps, arcsec" << deltaEps*180./M_PI*3600. << "deltaPsi" << deltaPsi*180./M_PI*3600.;
1687 			// Note: The sign for zrotation(-deltaPsi) was suggested by email by German Marques 2020-05-28 who referred to the SOFA library also used in Stellarium Web. This is then also ExplanSup3rd, 6.41.
1688 			Mat4d nut2000B=Mat4d::xrotation(eps_A) * Mat4d::zrotation(-deltaPsi)* Mat4d::xrotation(-eps_A-deltaEps); // eq.21 in Hilton et al. wrongly had a positive deltaPsi rotation.
1689 			rotLocalToParent=rotLocalToParent*nut2000B;
1690 		}
1691 		return;
1692 	}
1693 
1694 	if (re.method==RotationElements::WGCCRE)
1695 	{
1696 		const double t=(JDE-J2000);
1697 		const double T=t/36525.0;
1698 		double J2000NPoleRA=re.ra0+re.ra1*T;
1699 		double J2000NPoleDE=re.de0+re.de1*T;
1700 
1701 		// Apply detailed corrections from ExplSup2013 and WGCCRE2009/WGCCRE2015.
1702 		// Maybe later: With DE43x, get orientation from ephemeris lookup.
1703 		re.corrOri(t, T, &J2000NPoleRA, &J2000NPoleDE);
1704 
1705 		// keep for computation of central meridian etc.
1706 		re.currentAxisRA=J2000NPoleRA;
1707 		re.currentAxisDE=J2000NPoleDE;
1708 
1709 		// The next call ONLY sets rotLocalToParent
1710 		setRotEquatorialToVsop87(StelCore::matJ2000ToVsop87              // From VSOP87 into ICRS
1711 					 * Mat4d::zrotation(J2000NPoleRA+M_PI_2) // rotate along ICRS EQUATOR to ascending node
1712 					 * Mat4d::xrotation(M_PI_2-J2000NPoleDE) // node angle
1713 					 );
1714 		//debugAid=QString("Axis in ICRF: &alpha;: %1 &delta;: %2, W: %3<br/>").arg(StelUtils::radToDecDegStr(J2000NPoleRA), StelUtils::radToDecDegStr(J2000NPoleDE), QString::number(re.currentAxisW, 'f', 3));
1715 	}
1716 	else //	RotationElements::Traditional
1717 	{
1718 		// 0.21+: This used to be the old solution. Those axes were defined w.r.t. J2000 Ecliptic (VSOP87)
1719 		// Also here, the preliminary version for Earth's precession was modelled, before the Vondrak2011 model which came in V0.14.
1720 		// No other Planet had precessionRate defined, so it's safe to remove it here.
1721 		//rotLocalToParent = Mat4d::zrotation(re.ascendingNode - re.precessionRate*(JDE-re.epoch)) * Mat4d::xrotation(re.obliquity);
1722 		rotLocalToParent = Mat4d::zrotation(re.ascendingNode) * Mat4d::xrotation(re.obliquity);
1723 		//debugAid=QString("Axis (OLDSTYLE): re.obliquity=%1, re.ascendingNode=%2, axisrotation=%3<br/>").arg(StelUtils::radToDecDegStr(re.obliquity), StelUtils::radToDecDegStr(re.ascendingNode), QString::number(axisRotation, 'f', 3));
1724 	}
1725 	//addToExtraInfoString(DebugAid, debugAid);
1726 }
1727 
1728 // Retrieve planetocentric rectangular coordinates of a location on the ellipsoid surface
1729 // Meeus, Astr. Alg. 2nd ed, Ch.11.
1730 // @return [rhoCosPhiPrime*a, rhoSinPhiPrime*a, phiPrime, rho*a] where a=equatorial radius
getRectangularCoordinates(const double longDeg,const double latDeg,const double altMetres) const1731 Vec4d Planet::getRectangularCoordinates(const double longDeg, const double latDeg, const double altMetres) const
1732 {
1733 	if (getPlanetType()==Planet::isArtificial || getPlanetType()==Planet::isObserver || getEnglishName().contains("Spaceship", Qt::CaseInsensitive))
1734 		return Vec4d(0.);
1735 
1736 	// We may extend the use of this method later.
1737 	Q_UNUSED(longDeg)
1738 	const double a = getEquatorialRadius();
1739 	const double bByA = qMin(1., getOneMinusOblateness()); // b/a;
1740 	//qDebug() << "Planet" << englishName << "1-obl" << bByA << "or " << oneMinusOblateness;
1741 	Q_ASSERT(bByA<=1.);
1742 
1743 	// See some previous issues at https://github.com/Stellarium/stellarium/issues/391
1744 	// For unclear reasons latDeg can be nan. Safety measure:
1745 	const double latRad = std::isnan(latDeg) ? 0. : latDeg*M_PI_180;
1746 	Q_ASSERT_X(!std::isnan(latRad), "Planet.cpp", QString("NaN result for latRad. Object %1 latitude %2").arg(englishName).arg(QString::number(latDeg, 'f', 5)).toLatin1());
1747 	const double u = (abs(latRad) - M_PI_2 < 1e-10 ? latRad : atan( bByA * tan(latRad)) );
1748 	//qDebug() << "getTopographicOffsetFromCenter: a=" << a*AU << "b/a=" << bByA << "b=" << bByA*a *AU  << "latRad=" << latRad << "u=" << u;
1749 	// There seem to be numerical issues around tan/atan. Relieve the test a bit.
1750 	Q_ASSERT_X( fabs(u)-fabs(latRad) <= 1e-10, "Planet.cpp", QString("u: %1 latRad: %2 bByA: %3 latRad-u: %4 (%5)")
1751 								      .arg(QString::number(u))
1752 								      .arg(QString::number(latRad))
1753 								      .arg(QString::number(bByA, 'f', 10))
1754 								      .arg(QString::number(latRad-u))
1755 								      .arg(englishName).toLatin1() );
1756 	const double altFix = altMetres/(1000.0*AU*a);
1757 
1758 	const double rhoSinPhiPrime= bByA * sin(u) + altFix*sin(latRad);
1759 	const double rhoCosPhiPrime=        cos(u) + altFix*cos(latRad);
1760 
1761 	const double rho = sqrt(rhoSinPhiPrime*rhoSinPhiPrime+rhoCosPhiPrime*rhoCosPhiPrime);
1762 	double phiPrime=asin(rhoSinPhiPrime/rho);
1763 	return Vec4d(rhoCosPhiPrime*a, rhoSinPhiPrime*a, phiPrime, rho*a);
1764 }
1765 
1766 
getRotEquatorialToVsop87(void) const1767 Mat4d Planet::getRotEquatorialToVsop87(void) const
1768 {
1769 	Mat4d rval = rotLocalToParent;
1770 	if (re.method==RotationElements::Traditional)
1771 	{
1772 		if (parent)
1773 		{
1774 			for (PlanetP p=parent;p->parent;p=p->parent)
1775 			{
1776 				// The Sun is the ultimate parent. However, we don't want its matrix!
1777 				if (p->pType!=isStar)
1778 					rval = p->rotLocalToParent * rval;
1779 			}
1780 		}
1781 	}
1782 	return rval;
1783 }
1784 
setRotEquatorialToVsop87(const Mat4d & m)1785 void Planet::setRotEquatorialToVsop87(const Mat4d &m)
1786 {
1787 	switch (re.method) {
1788 		case RotationElements::Traditional:
1789 		{
1790 			Mat4d a = Mat4d::identity();
1791 			if (parent)
1792 			{
1793 				for (PlanetP p=parent;p->parent;p=p->parent)
1794 				{
1795 					// The Sun is the ultimate parent. However, we don't want its matrix!
1796 					if (p->pType!=isStar)
1797 					{
1798 						addToExtraInfoString(DebugAid, QString("This involves localToParent of %1 <br/>").arg(p->englishName));
1799 						a = p->rotLocalToParent * a;
1800 					}
1801 				}
1802 			}
1803 			rotLocalToParent = a.transpose() * m;
1804 		}
1805 			break;
1806 		case RotationElements::WGCCRE:
1807 			rotLocalToParent = m;
1808 			break;
1809 	}
1810 }
1811 
1812 
1813 // Compute the axial z rotation (daily rotation around the polar axis) [degrees] to use from equatorial to hour angle based coordinates.
1814 // On Earth, sidereal time on the other hand is the angle along the planet equator from RA0 to the meridian, i.e. hour angle of the first point of Aries.
1815 // For Earth (of course) it is sidereal time at Greenwich.
1816 // V0.21+ update:
1817 // For planets and Moons, in this context this is the rotation angle W of the Prime meridian from the ascending node of the planet equator on the ICRF equator.
1818 // The usual WGCCRE model is W=W0+d*W1. Some planets/moons have more complicated rotations though, these are also handled in here.
1819 // The planet objects with old-style data are computed like in earlier versions of Stellarium. Their computational model is however questionable.
1820 // We need both JD and JDE here for Earth. (For other planets only JDE.)
getSiderealTime(double JD,double JDE) const1821 double Planet::getSiderealTime(double JD, double JDE) const
1822 {
1823 	if (englishName=="Earth")
1824 	{	// Check to make sure that nutation is just those few arcseconds.
1825 		if (StelApp::getInstance().getCore()->getUseNutation())
1826 			return get_apparent_sidereal_time(JD, JDE); // degrees
1827 		else
1828 			return get_mean_sidereal_time(JD, JDE); // degrees
1829 	}
1830 
1831 	// V0.21+: new rotational values from ExplSup2013 or WGCCRE2009/2015.
1832 	if (re.method==RotationElements::WGCCRE)
1833 	{
1834 		// This returns angle W, the longitude of the prime meridian measured along the planet equator
1835 		// from the ascending node (intersection) of the planet equator with the ICRF equator.
1836 		const double t=JDE-J2000;
1837 		const double T=t/36525.0;
1838 		double w=re.W0+remainder(t*re.W1, 360.); // W is given and also returned in degrees, clamped to small angles so that adding small corrections makes sense.
1839 		w+=re.corrW(t, T); // Apply the bespoke corrections from Explanatory Supplement 2013/WGCCRE2009/WGCCRE2015.
1840 		return w;
1841 	}
1842 
1843 	// OLD MODEL, BEFORE V0.21
1844 	// This is still used for a few solar system objects where we don't have modern elements from the WGCCRE.
1845 
1846 	const double t = JDE - re.epoch;
1847 	// avoid division by zero (typical case for moons with chaotic period of rotation)
1848 	double rotations = (re.period==0. ? 1.  // moon with chaotic period of rotation
1849 					  : t / static_cast<double>(re.period));
1850 	rotations = remainder(rotations, 1.0); // remove full rotations to limit angle.
1851 	return rotations * 360. + static_cast<double>(re.offset);
1852 }
1853 
1854 // Get duration of mean solar day (in earth days)
getMeanSolarDay() const1855 double Planet::getMeanSolarDay() const
1856 {
1857 	double msd = 0.;
1858 
1859 	if (englishName=="Sun")
1860 	{
1861 		// A mean solar day (equals to Earth's day) has been added here for educational purposes
1862 		// Details: https://sourceforge.net/p/stellarium/discussion/278769/thread/fbe282db/
1863 		return 1.;
1864 	}
1865 
1866 	const double sday = getSiderealDay();
1867 	const double coeff = qAbs(sday/getSiderealPeriod());
1868 	double sign = 1.;
1869 	// planets with retrograde rotation
1870 	if (englishName=="Venus" || englishName=="Uranus" || englishName=="Pluto")
1871 		sign = -1.;
1872 
1873 	if (pType==Planet::isMoon)
1874 	{
1875 		// duration of mean solar day on moon are same as synodic month on this moon
1876 		const double a = parent->getSiderealPeriod()/sday;
1877 		msd = sday*(a/(a-1));
1878 	}
1879 	else
1880 		msd = sign*sday/(1 - sign*coeff);
1881 
1882 	return msd;
1883 }
1884 
1885 // Get the Planet position in Cartesian ecliptic (J2000) coordinates in AU, centered on the parent Planet.
1886 // This is only needed for orbit drawing.
getEclipticPos(double dateJDE) const1887 Vec3d Planet::getEclipticPos(double dateJDE) const
1888 {
1889 	// Use current position if the time match.
1890 	if (fuzzyEquals(dateJDE, lastJDE))
1891 		return eclipticPos;
1892 
1893 	// Otherwise try to use a cached position.
1894 	Vec3d *pos=orbitPositionsCache[dateJDE];
1895 	if (!pos)
1896 	{
1897 		pos = new Vec3d;
1898 		Vec3d velDummy;
1899 		coordFunc(dateJDE, *pos, velDummy, orbitPtr);
1900 		orbitPositionsCache.insert(dateJDE, pos);
1901 	}
1902 	return *pos;
1903 }
1904 
1905 // Return heliocentric ecliptical coordinate of p [AU]
getHeliocentricPos(Vec3d p) const1906 Vec3d Planet::getHeliocentricPos(Vec3d p) const
1907 {
1908 	// Note: using shared copies is too slow here.  So we use direct access instead.
1909 	Vec3d pos = p;
1910 	const Planet* pp = parent.data();
1911 	if (pp)
1912 	{
1913 		while (pp->parent.data())
1914 		{
1915 			pos += pp->eclipticPos;
1916 			pp = pp->parent.data();
1917 		}
1918 	}
1919 	return pos;
1920 }
1921 
getHeliocentricEclipticPos(double dateJDE) const1922 Vec3d Planet::getHeliocentricEclipticPos(double dateJDE) const
1923 {
1924 	Vec3d pos = getEclipticPos(dateJDE);
1925 	const Planet* pp = parent.data();
1926 	if (pp)
1927 	{
1928 		while (pp->parent.data())
1929 		{
1930 			pos += pp->getEclipticPos(dateJDE);
1931 			pp = pp->parent.data();
1932 		}
1933 	}
1934 	return pos;
1935 }
1936 
setHeliocentricEclipticPos(const Vec3d & pos)1937 void Planet::setHeliocentricEclipticPos(const Vec3d &pos)
1938 {
1939 	eclipticPos = pos;
1940 	PlanetP p = parent;
1941 	if (p)
1942 	{
1943 		while (p->parent)
1944 		{
1945 			eclipticPos -= p->eclipticPos;
1946 			p = p->parent;
1947 		}
1948 	}
1949 }
1950 // Return heliocentric velocity of planet.
getHeliocentricEclipticVelocity() const1951 Vec3d Planet::getHeliocentricEclipticVelocity() const
1952 {
1953 	// Note: using shared copies is too slow here.  So we use direct access instead.
1954 	Vec3d vel = eclipticVelocity;
1955 	const Planet* pp = parent.data();
1956 	if (pp)
1957 	{
1958 		while (pp->parent.data())
1959 		{
1960 			vel += pp->eclipticVelocity;
1961 			pp = pp->parent.data();
1962 		}
1963 	}
1964 	return vel;
1965 }
1966 
1967 // Compute the distance to the given position in heliocentric coordinate (in AU)
1968 // This is called by SolarSystem::draw()
computeDistance(const Vec3d & obsHelioPos)1969 double Planet::computeDistance(const Vec3d& obsHelioPos)
1970 {
1971 	distance = (obsHelioPos-getHeliocentricEclipticPos()).length();
1972 	// improve fps by juggling updates for asteroids and other minor bodies. They must be fast if close to observer, but can be slow if further away.
1973 	if (pType >= Planet::isAsteroid)
1974 		deltaJDE=distance*StelCore::JD_SECOND;
1975 	return distance;
1976 }
1977 
1978 // Get the phase angle (radians) for an observer at pos obsPos in heliocentric coordinates (dist in AU)
getPhaseAngle(const Vec3d & obsPos) const1979 double Planet::getPhaseAngle(const Vec3d& obsPos) const
1980 {
1981 	const double observerRq = obsPos.lengthSquared();
1982 	const Vec3d& planetHelioPos = getHeliocentricEclipticPos();
1983 	const double planetRq = planetHelioPos.lengthSquared();
1984 	const double observerPlanetRq = (obsPos - planetHelioPos).lengthSquared();
1985 	return std::acos((observerPlanetRq + planetRq - observerRq)/(2.0*std::sqrt(observerPlanetRq*planetRq)));
1986 }
1987 
1988 // Get the planet phase ([0..1] illuminated fraction of the planet disk) for an observer at pos obsPos in heliocentric coordinates (in AU)
getPhase(const Vec3d & obsPos) const1989 float Planet::getPhase(const Vec3d& obsPos) const
1990 {
1991 	const double observerRq = obsPos.lengthSquared();
1992 	const Vec3d& planetHelioPos = getHeliocentricEclipticPos();
1993 	const double planetRq = planetHelioPos.lengthSquared();
1994 	const double observerPlanetRq = (obsPos - planetHelioPos).lengthSquared();
1995 	const double cos_chi = (observerPlanetRq + planetRq - observerRq)/(2.0*std::sqrt(observerPlanetRq*planetRq));
1996 	return 0.5f * static_cast<float>(qAbs(1. + cos_chi));
1997 }
1998 
getPAsun(const Vec3d & sunPos,const Vec3d & objPos)1999 float Planet::getPAsun(const Vec3d &sunPos, const Vec3d &objPos)
2000 {
2001 	float ra0, de0, ra, de, dra;
2002 	StelUtils::rectToSphe(&ra0, &de0, sunPos);
2003 	StelUtils::rectToSphe(&ra, &de, objPos);
2004 	dra=ra0-ra;
2005 	return atan2f(cos(de0)*sin(dra), sin(de0)*cos(de) - cos(de0)*sin(de)*cos(dra));
2006 }
2007 
2008 
2009 // Get planetographic coordinates of subsolar and sub-observer points.
2010 // Source: Explanatory Supplement 2013, 10.4.1
2011 // Erroneous expression 10.27 fixed by Explan. Suppl. 1992, 7.12-26.
getSubSolarObserverPoints(const StelCore * core,bool jupiterGraphical) const2012 QPair<Vec4d, Vec3d> Planet::getSubSolarObserverPoints(const StelCore *core, bool jupiterGraphical) const
2013 {
2014 //	QString debugAid;
2015 	QPair<Vec4d, Vec3d>ret;
2016 	// In this case Precession/Nutation matrix has to be built as written in the books, not as made in other places in the program
2017 	double eps_A, chi_A, omega_A, psi_A;
2018 	getPrecessionAnglesVondrak(core->getJDE(), &eps_A, &chi_A, &omega_A, &psi_A);
2019 	// Standard formulation from Explanatory Supplement 2013, 6.28.
2020 	// NOTE: For higher accuracy, there may be need for adding a Frame Bias rotation, but this should influence results by sub-arseconds.
2021 	Mat4d PrecNut= Mat4d::zrotation(chi_A)*Mat4d::xrotation(-omega_A)*Mat4d::zrotation(-psi_A)*Mat4d::xrotation(EPS_0*M_PI_180);
2022 	if (core->getUseNutation())
2023 	{
2024 		double deltaEps, deltaPsi;
2025 		getNutationAngles(core->getJDE(), &deltaPsi, &deltaEps);
2026 		Mat4d nut2000B=Mat4d::xrotation(-eps_A-deltaEps) * Mat4d::zrotation(-deltaPsi) * Mat4d::xrotation(eps_A);
2027 		PrecNut = nut2000B*PrecNut;
2028 	}
2029 
2030 	const double f=1.-oneMinusOblateness; // flattening term
2031 	const double fTerm=1-f*f;
2032 	// When using the last computed elements, light time should already be accounted for.
2033 	const Vec3d r  = PrecNut*StelCore::matVsop87ToJ2000*getHeliocentricEclipticPos();
2034 	const Vec3d r_e= PrecNut*StelCore::matVsop87ToJ2000*core->getCurrentPlanet()->getHeliocentricEclipticPos();
2035 	const Vec3d Dr= r-r_e; // should be regular vector resembling RA/DE of object in rectangular equatorial PrecNut*(J2000) coords.
2036 //	// verify this assumption...
2037 //	double ra, de;
2038 //	StelUtils::rectToSphe(&ra, &de, r); ra=StelUtils::fmodpos(ra, 2.*M_PI);
2039 //	debugAid.append(QString("r: &alpha;=%1=%2, &delta;=%3=%4 <br/>").arg(
2040 //				StelUtils::radToDecDegStr(ra), StelUtils::radToHmsStr(ra),
2041 //				StelUtils::radToDecDegStr(de), StelUtils::radToDmsStr(de)));
2042 //	StelUtils::rectToSphe(&ra, &de, r_e); ra=StelUtils::fmodpos(ra, 2.*M_PI);
2043 //	debugAid.append(QString("r<sub>e</sub>: &alpha;=%1=%2, &delta;=%3=%4 <br/>").arg(
2044 //				StelUtils::radToDecDegStr(ra), StelUtils::radToHmsStr(ra),
2045 //				StelUtils::radToDecDegStr(de), StelUtils::radToDmsStr(de)));
2046 //	StelUtils::rectToSphe(&ra, &de, Dr); ra=StelUtils::fmodpos(ra, 2.*M_PI);
2047 //	debugAid.append(QString("&Delta;r: &alpha;=%1=%2, &delta;=%3=%4 <br/>").arg(
2048 //				StelUtils::radToDecDegStr(ra), StelUtils::radToHmsStr(ra),
2049 //				StelUtils::radToDecDegStr(de), StelUtils::radToDmsStr(de)));
2050 
2051 	Vec3d s=-r;  s.normalize();
2052 	Vec3d e=-Dr; e.normalize();
2053 	const double sina0=sin(re.currentAxisRA);
2054 	const double cosa0=cos(re.currentAxisRA);
2055 	const double sind0=sin(re.currentAxisDE);
2056 	const double cosd0=cos(re.currentAxisDE);
2057 	// sub-earth point (10.19)
2058 	Vec3d n=PrecNut*Vec3d(cosd0*cosa0, cosd0*sina0, sind0);
2059 	// Rotation W is OK for all planets except Jupiter: return simple W_II to remove GRS adaptation shift.
2060 	const double W= ( ((englishName=="Jupiter") && !jupiterGraphical )  ?
2061 			re.W0+ remainder( (core->getJDE()-J2000 - Dr.length()*(AU/(SPEED_OF_LIGHT*86400.)))*re.W1, 360.) :
2062 			re.currentAxisW);
2063 	const double sinW=sin(W*M_PI_180);
2064 	const double sindw=sinW*cosd0;
2065 	const double cosdw=cos(asin(sindw));
2066 	const double sinpsi=sinW*sind0/cosdw;
2067 	const double cospsi=cos(W*M_PI_180)/cosdw;
2068 	const double psi=atan2(sinpsi, cospsi);
2069 	const double aw=re.currentAxisRA+M_PI_2+psi;
2070 	const Vec3d w=PrecNut*Vec3d(cosdw*cos(aw), cosdw*sin(aw), sindw);
2071 	const Vec3d y=w^n;
2072 	const Vec3d subEarth(e.dot(w), e.dot(y), e.dot(n)); // 10.25
2073 	const double phi_e=asin(subEarth[2]);               // 10.26
2074 	const double phiP_e=atan(tan(phi_e)/fTerm);
2075 	double lambdaP_e=StelUtils::fmodpos(atan2(subEarth[1], subEarth[0]), 2.0*M_PI);
2076 	if (re.W1<0) lambdaP_e=2.*M_PI-lambdaP_e;
2077 
2078 	// PA of axis: 10.29 with elements of P from 10.28, but fixed error in Explan.Sup.2013 with Explan.Sup.1992!
2079 	const Vec3d P(n.dot(e), n.dot(e^Vec3d(0., 0., 1.)), n.dot((e^Vec3d(0., 0., 1.))^e));
2080 
2081 	ret.first.set(phi_e, phiP_e, lambdaP_e, StelUtils::fmodpos(atan(P.v[1]/ P.v[2]), 2.*M_PI));
2082 
2083 	// Subsolar point:
2084 	const Vec3d subSol(s.dot(w), s.dot(y), s.dot(n));
2085 	const double phi_s=asin(subSol[2]);
2086 	const double phiP_s=atan2(tan(phi_s), fTerm);
2087 	double lambdaP_s=StelUtils::fmodpos(atan2(subSol[1], subSol[0]), 2.0*M_PI);
2088 	if (re.W1<0) lambdaP_s=2.*M_PI-lambdaP_s;
2089 
2090 	ret.second.set(phi_s, phiP_s, lambdaP_s);
2091 
2092 //	debugAid.append(QString("&phi;<sub>e</sub>: %1, &phi;'<sub>e</sub>: %2, &lambda;<sub>e</sub>: %3, PA<sub>n</sub>: %4<br/>").arg(
2093 //			StelUtils::radToDecDegStr(ret.first[0]),
2094 //			StelUtils::radToDecDegStr(ret.first[1]),
2095 //			StelUtils::radToDecDegStr(StelUtils::fmodpos(ret.first[2], 2.*M_PI)),
2096 //			StelUtils::radToDecDegStr(StelUtils::fmodpos(ret.first[3], 2.*M_PI))
2097 //		       ));
2098 //	debugAid.append(QString("&phi;<sub>s</sub>: %1, &phi;'<sub>s</sub>: %2, &lambda;<sub>s</sub>: %3<br/>").arg(
2099 //			StelUtils::radToDecDegStr(ret.second[0]),
2100 //			StelUtils::radToDecDegStr(ret.second[1]),
2101 //			StelUtils::radToDecDegStr(StelUtils::fmodpos(ret.second[2], 2.*M_PI))
2102 //		       ));
2103 //
2104 //	StelObjectMgr& objMgr = StelApp::getInstance().getStelObjectMgr();
2105 //		if (objMgr.getSelectedObject().length()>0)
2106 //			objMgr.getSelectedObject()[0]->addToExtraInfoString(StelObject::DebugAid, debugAid);
2107 	return ret;
2108 }
2109 
2110 
2111 // Get the elongation angle (radians) for an observer at pos obsPos in heliocentric coordinates (dist in AU)
getElongation(const Vec3d & obsPos) const2112 double Planet::getElongation(const Vec3d& obsPos) const
2113 {
2114 	const double observerRq = obsPos.lengthSquared();
2115 	const Vec3d& planetHelioPos = getHeliocentricEclipticPos();
2116 	const double planetRq = planetHelioPos.lengthSquared();
2117 	const double observerPlanetRq = (obsPos - planetHelioPos).lengthSquared();
2118 	return std::acos((observerPlanetRq  + observerRq - planetRq)/(2.0*std::sqrt(observerPlanetRq*observerRq)));
2119 }
2120 
2121 // Source: Explanatory Supplement 2013, Table 10.6 and formula (10.5) with semimajorAxis a from Table 8.7.
getMeanOppositionMagnitude() const2122 float Planet::getMeanOppositionMagnitude() const
2123 {
2124 	if (absoluteMagnitude<=-99.f)
2125 		return 100.f;
2126 
2127 	static const QMap<QString, float>momagMap = {
2128 		{ "Sun",    100.f},
2129 		{ "Moon",   -12.74f},
2130 		{ "Mars",    -2.01f},
2131 		{ "Jupiter", -2.7f},
2132 		{ "Saturn",   0.67f},
2133 		{ "Uranus",   5.52f},
2134 		{ "Neptune",  7.84f},
2135 		{ "Pluto",   15.12f},
2136 		{ "Io",       5.02f},
2137 		{ "Europa",   5.29f},
2138 		{ "Ganymede", 4.61f},
2139 		{ "Callisto", 5.65f}};
2140 	if (momagMap.contains(englishName))
2141 		return momagMap.value(englishName);
2142 
2143 	static const QMap<QString, double>smaMap = {
2144 		{ "Mars",     1.52371034 },
2145 		{ "Jupiter",  5.202887   },
2146 		{ "Saturn",   9.53667594 },
2147 		{ "Uranus",  19.18916464 },
2148 		{ "Neptune", 30.06992276 },
2149 		{ "Pluto",   39.48211675 }};
2150 	double semimajorAxis=smaMap.value(parent->englishName, 0.);
2151 	if (pType>= isAsteroid)
2152 	{
2153 		Q_ASSERT(orbitPtr);
2154 		if (orbitPtr)
2155 			semimajorAxis=static_cast<KeplerOrbit*>(orbitPtr)->getSemimajorAxis();
2156 		else
2157 			qDebug() << "WARNING: No orbitPtr for " << englishName;
2158 	}
2159 
2160 	if (semimajorAxis>0.)
2161 		return absoluteMagnitude+5.f*static_cast<float>(log10(semimajorAxis*(semimajorAxis-1.)));
2162 
2163 	return 100.;
2164 }
2165 
2166 // Computation of the visual magnitude (V band) of the planet.
getVMagnitude(const StelCore * core) const2167 float Planet::getVMagnitude(const StelCore* core) const
2168 {
2169 	if (parent == Q_NULLPTR)
2170 	{
2171 		// Sun, compute the apparent magnitude for the absolute mag (V: 4.83) and observer's distance
2172 		// Hint: Absolute Magnitude of the Sun in Several Bands: http://mips.as.arizona.edu/~cnaw/sun.html
2173 		const double distParsec = std::sqrt(core->getObserverHeliocentricEclipticPos().lengthSquared())*AU/PARSEC;
2174 
2175 		// check how much of it is visible
2176 		const double shadowFactor = qMax(0.000128, GETSTELMODULE(SolarSystem)->getSolarEclipseFactor(core).first);
2177 		// See: Hughes, D. W., Brightness during a solar eclipse // Journal of the British Astronomical Association, vol.110, no.4, p.203-205
2178 		// URL: http://adsabs.harvard.edu/abs/2000JBAA..110..203H
2179 
2180 		return static_cast<float>(4.83 + 5.*(std::log10(distParsec)-1.) - 2.5*(std::log10(shadowFactor)));
2181 	}
2182 
2183 	// Compute the phase angle i. We need the intermediate results also below, therefore we don't just call getPhaseAngle.
2184 	const Vec3d& observerHelioPos = core->getObserverHeliocentricEclipticPos();
2185 	const double observerRq = observerHelioPos.lengthSquared();
2186 	const Vec3d& planetHelioPos = getHeliocentricEclipticPos();
2187 	const double planetRq = planetHelioPos.lengthSquared();
2188 	const double observerPlanetRq = (observerHelioPos - planetHelioPos).lengthSquared();
2189 	const double dr = std::sqrt(observerPlanetRq*planetRq);
2190 	const double cos_chi = (observerPlanetRq + planetRq - observerRq)/(2.0*dr);
2191 	const double phaseAngle = std::acos(cos_chi);
2192 
2193 	double shadowFactor = 1.;
2194 	// Check if the satellite is inside the inner shadow of the parent planet:
2195 	if (parent->parent != Q_NULLPTR)
2196 	{
2197 		const Vec3d& parentHeliopos = parent->getHeliocentricEclipticPos();
2198 		const double parent_Rq = parentHeliopos.lengthSquared();
2199 		const double pos_times_parent_pos = planetHelioPos * parentHeliopos;
2200 		if (pos_times_parent_pos > parent_Rq)
2201 		{
2202 			// The satellite is farther away from the sun than the parent planet.
2203 			if (englishName=="Moon")
2204 			{
2205 				static const double totalityFactor=2.710e-5; // defined previously by AW
2206 				const SolarSystem* ssm = GETSTELMODULE(SolarSystem);
2207 				const QPair<Vec3d,Vec3d>shadowRadii=ssm->getEarthShadowRadiiAtLunarDistance();
2208 				const double dist=getEclipticPos().length();  // Lunar distance [AU]
2209 				const double u=shadowRadii.first[0]  / 3600.; // geocentric angle of earth umbra radius at lunar distance [degrees]
2210 				const double p=shadowRadii.second[0] / 3600.; // geocentric angle of earth penumbra radius at lunar distance [degrees]
2211 				const double r=atan(getEquatorialRadius()/dist) * M_180_PI; // geocentric angle of Lunar radius at lunar distance [degrees]
2212 
2213 				// We must compute an elongation from the aberrated sun. The following is adapted from getElongation(), with a tweak to move the Sun to its apparent position.
2214 				PlanetP sun=ssm->getSun();
2215 				const Vec3d obsPos=parent->eclipticPos-sun->getAberrationPush();
2216 				const double observerRq = obsPos.lengthSquared();
2217 				const Vec3d& planetHelioPos = getHeliocentricEclipticPos() - sun->getAberrationPush();
2218 				const double planetRq = planetHelioPos.lengthSquared();
2219 				const double observerPlanetRq = dist*dist; // (obsPos - planetHelioPos).lengthSquared();
2220 				double aberratedElongation = std::acos((observerPlanetRq  + observerRq - planetRq)/(2.0*std::sqrt(observerPlanetRq*observerRq)));
2221 				const double od = 180. - aberratedElongation * (180.0/M_PI); // opposition distance [degrees]
2222 
2223 				if (od>p+r) shadowFactor=1.0;
2224 				else if (od>u+r) // penumbral transition zone: gradual decline (square curve)
2225 					shadowFactor=0.6+0.4*sqrt((od-u-r)/(p-u));
2226 				else if (od>u-r) // umbral transition zone
2227 					shadowFactor=totalityFactor+(0.6-totalityFactor)*(od-u+r)/(2.*r);
2228 				else // totality. Still, center is darker...
2229 				{
2230 					// Fit a more realistic magnitude for the Moon case.
2231 					// I used some empirical data for fitting. --AW
2232 					// TODO: This factor should be improved!
2233 					shadowFactor=totalityFactor*0.5*(1+od/(u-r));
2234 				}
2235 			}
2236 			else
2237 			{
2238 				const double sun_radius = parent->parent->equatorialRadius;
2239 				const double sun_minus_parent_radius = sun_radius - parent->equatorialRadius;
2240 				const double quot = pos_times_parent_pos/parent_Rq;
2241 
2242 				// Compute d = distance from satellite center to border of inner shadow.
2243 				// d>0 means inside the shadow cone.
2244 				double d = sun_radius - sun_minus_parent_radius*quot - std::sqrt((1.-sun_minus_parent_radius/std::sqrt(parent_Rq)) * (planetRq-pos_times_parent_pos*quot));
2245 				if (d>=equatorialRadius)
2246 				{
2247 					// The satellite is totally inside the inner shadow.
2248 					shadowFactor = 1e-9;
2249 				}
2250 				else if (d>-equatorialRadius)
2251 				{
2252 					// The satellite is partly inside the inner shadow,
2253 					// compute a fantasy value for the magnitude:
2254 					d /= equatorialRadius;
2255 					shadowFactor = (0.5 - (std::asin(d)+d*std::sqrt(1.0-d*d))/M_PI);
2256 				}
2257 			}
2258 		}
2259 	}
2260 
2261 	// Lunar Magnitude from Earth: This is a combination of Russell 1916 (!) with its albedo dysbalance, Krisciunas-Schaefer (1991) for the opposition surge, and Agrawal (2016) for the contribution of earthshine.
2262 	if ((core->getCurrentLocation().planetName=="Earth") && (englishName=="Moon"))
2263 	{
2264 		const Vec3d solarAberrationPush=GETSTELMODULE(SolarSystem)->getSun()->getAberrationPush();
2265 		double lEarth, bEarth, lMoon, bMoon;
2266 		StelUtils::rectToSphe(&lEarth, &bEarth, observerHelioPos-solarAberrationPush);
2267 		StelUtils::rectToSphe(&lMoon, &bMoon, eclipticPos);
2268 		double dLong=StelUtils::fmodpos(lMoon-lEarth, 2.*M_PI); if (dLong>M_PI) dLong-=2.*M_PI; // now dLong<0 for waxing phases.
2269 		const double p=dLong*M_180_PI;
2270 		// main magnitude term from Russell 1916. Polynomes from Excel fitting with mag(dLong=180)=0.
2271 		// Measurements support only dLong -150...150, and the New Moon area is mere guesswork.
2272 		double magIll=(p<0 ?
2273 				(((((4.208547E-12*p + 1.754857E-09)*p + 2.749700E-07)*p + 1.860811E-05)*p + 5.590310E-04)*p - 1.628691E-02)*p + 4.807056E-03 :
2274 				(((((4.609790E-12*p - 1.977692E-09)*p + 3.305454E-07)*p - 2.582825E-05)*p + 9.593360E-04)*p + 1.213761E-02)*p + 7.710015E-03);
2275 		magIll-=12.73;
2276 		static const double rf=2.56e-6; // Reference flux [lx] from Agrawal (14)
2277 		double fluxIll=rf*pow(10., -0.4*magIll);
2278 
2279 		// apply opposition surge where needed
2280 		const double psi=getPhaseAngle(observerHelioPos);
2281 		const double surge=qMax(1., 1.35-2.865*abs(psi));
2282 		fluxIll *= surge; // This is now shape of Russell's magnitude curve with peak brightness matched with Krisciunas-Schaefer
2283 		// apply distance factor
2284 		static const double lunarMeanDist=384399./AU;
2285 		static const double lunarMeanDistSq=lunarMeanDist*lunarMeanDist;
2286 		fluxIll *= (lunarMeanDistSq/observerPlanetRq);
2287 
2288 		// compute flux of ashen light: Agrawal 2016.
2289 		const double beta=parent->equatorialRadius*parent->equatorialRadius/eclipticPos.lengthSquared();
2290 		const double gamma=equatorialRadius*equatorialRadius/eclipticPos.lengthSquared();
2291 
2292 		const double slfoe=133100.; // https://www.allthingslighting.org/index.php/2019/02/15/solar-illumination/
2293 		const double LumEarth=slfoe * static_cast<double>(core->getCurrentObserver()->getHomePlanet()->albedo);
2294 		const double elfom=LumEarth*beta;
2295 		const double elfoe=elfom*static_cast<double>(albedo)*gamma; // brightness of full earthshine.
2296 		const double pfac=1.-(0.5*(1.+cos(dLong))); // diminishing earthshine with phase angle
2297 		const double fluxTotal=fluxIll + elfoe*pfac;
2298 		return -2.5f*static_cast<float>(log10(fluxTotal*shadowFactor/rf));
2299 	}
2300 
2301 	// Use empirical formulae for main planets when seen from earth. MallamaHilton_2018 also work from other locations.
2302 	if ((Planet::getApparentMagnitudeAlgorithm()==MallamaHilton_2018) || (core->getCurrentLocation().planetName=="Earth"))
2303 	{
2304 		const double phaseDeg=phaseAngle*M_180_PI;
2305 		const double d = 5. * log10(dr);
2306 
2307 		// There are several solutions:
2308 		// (0) "ExplanatorySupplement_1992" original solution in Stellarium, present around 2010.
2309 		// (1) "Mueller_1893" G. Müller, based on visual observations 1877-91. [Expl.Suppl.1961 p.312ff]
2310 		// (2) "AstronomicalAlmanac_1984" Astronomical Almanac 1984 and later. These give V (instrumental) magnitudes.
2311 		//     The structure is almost identical, just the numbers are different!
2312 		//     Note that calling (2) "Harris" is an absolute misnomer. Meeus clearly describes this in AstrAlg1998 p.286.
2313 		// (3) "ExplanatorySupplement_2013" More modern.
2314 		// (4) "MallamaHilton_2018" seems the best available. Mercury-Neptune. Pluto and Jovian moons copied from (3).
2315 		switch (Planet::getApparentMagnitudeAlgorithm())
2316 		{
2317 			case UndefinedAlgorithm:	// The most recent solution should be activated by default
2318 			case MallamaHilton_2018:
2319 			{
2320 				if (englishName=="Mercury")
2321 					return static_cast<float>(-0.613 + d + ((((((-3.0334e-12*phaseDeg + 1.6893e-9)*phaseDeg -3.4265e-7)*phaseDeg) + 3.3644e-5)*phaseDeg - 1.6336e-3)*phaseDeg + 6.3280e-2)*phaseDeg);
2322 				if (englishName=="Venus")
2323 				{
2324 					if (phaseDeg<=163.7)
2325 						return static_cast<float>(-4.384 + d + (((8.938e-9*phaseDeg - 2.814e-6)*phaseDeg + 3.687e-4)*phaseDeg - 1.044e-3)*phaseDeg);
2326 					else
2327 						return static_cast<float>(236.05828 + d + (8.39034e-3*phaseDeg - 2.81914)*phaseDeg);
2328 				}
2329 				if (englishName=="Earth")
2330 					return static_cast<float>(-3.99 + d + ((2.054e-4*phaseDeg - 1.060e-3)*phaseDeg));
2331 				if (englishName=="Mars")
2332 				{
2333 					double V=d;
2334 					const QPair<Vec4d,Vec3d>axis=getSubSolarObserverPoints(core);
2335 					V+=re.getMarsMagLs(0.5*(axis.first[2]+axis.second[2]), true); // albedo effect
2336 					Q_ASSERT(abs(re.getMarsMagLs(0.5*(axis.first[2]+axis.second[2]), true)) < 0.2);
2337 					// determine orbital longitude
2338 					const Vec3d pos=getHeliocentricEclipticPos();
2339 					double lng, lat;
2340 					StelUtils::rectToSphe(&lng, &lat, pos);
2341 					const double orbLong=StelUtils::fmodpos(lng-getRotAscendingNode(), 2.*M_PI);
2342 					V+=re.getMarsMagLs(orbLong, false); // Orbital Longitude effect
2343 					Q_ASSERT(abs(re.getMarsMagLs(orbLong, false)) < 0.1 );
2344 					if(phaseDeg<=50)
2345 						V += (-0.0001302*phaseDeg + 0.02267)*phaseDeg -1.601;
2346 					else
2347 						V += ( 0.0003445*phaseDeg - 0.02573)*phaseDeg -0.367;
2348 					return static_cast<float>(V);
2349 				}
2350 				if (englishName=="Jupiter")
2351 				{
2352 					if (phaseDeg<=12)
2353 						return static_cast<float>(-9.395 + d + (6.16e-4*phaseDeg -3.7e-4)*phaseDeg);
2354 					else {
2355 						const double p= phaseDeg/180.;
2356 						const double bracket= 1.0 - (((((-1.876*p + 2.809)*p - 0.062)*p -0.363)*p -1.507)*p);
2357 						return static_cast<float>(-9.428 + d - 2.5*log10(bracket));
2358 					}
2359 				}
2360 				if (englishName=="Saturn")
2361 				{
2362 					if (phaseDeg<6.5)
2363 					{
2364 						// Note: this is really only for phaseAngle<6, i.e. from Earth.
2365 						// add rings computation
2366 						const QPair<Vec4d,Vec3d>axis=getSubSolarObserverPoints(core);
2367 						const double be=axis.first[0];
2368 						const double bs=axis.second[0];
2369 						double beta= be*bs; beta=(beta<=0 ? 0. : sqrt(beta));
2370 						return static_cast<float>(-8.914 + d + 0.026*phaseDeg - (1.825+0.378*exp(-2.25*phaseDeg))*sin(beta) );
2371 					}
2372 					else
2373 					{
2374 						// Expression (12). This gives magV for the globe only, no ring.
2375 						return static_cast<float>(-8.94+d+(((4.767e-9*phaseDeg-1.505e-6)*phaseDeg + 2.672e-4)*phaseDeg + 2.446e-4)*phaseDeg);
2376 					}
2377 				}
2378 				if (englishName=="Uranus")
2379 				{
2380 					const QPair<Vec4d,Vec3d>axis=getSubSolarObserverPoints(core);
2381 					const double phiP=0.5*M_180_PI*(abs(axis.first[1])+abs(axis.second[1]));
2382 
2383 					return static_cast<float>(-7.110 + d - 8.4e-4*phiP + (1.045e-4*phaseDeg+6.587e-3)*phaseDeg );
2384 				}
2385 				if (englishName=="Neptune")
2386 				{
2387 					int yy, mm, dd;
2388 					StelUtils::getDateFromJulianDay(core->getJD(), &yy, &mm, &dd);
2389 					const double t=StelUtils::yearFraction(yy, mm, dd);
2390 					double V=d-6.89;
2391 					if ((1980.0<=t) && (t<=2000.0))
2392 						V-=0.0054*(t-1980.);
2393 					else if (t>2000.0)
2394 						V-=0.11;
2395 
2396 					return static_cast<float>(V +(9.617e-5*phaseDeg +7.944e-3)*phaseDeg);
2397 				}
2398 				if (englishName=="Pluto")
2399 					return static_cast<float>(-1.01 + d);
2400 
2401 				// AW 2017: I've added special case for Jupiter's moons when they are in the shadow of Jupiter.
2402 				// TODO: Need experimental data to fitting to real world or the scientific paper with description of model.
2403 				// GZ 2017-09: Phase coefficients for I and III corrected, based on original publication (Stebbins&Jacobsen 1928) now.
2404 				// AW 2020-02: Let's use linear model in the first approximation for smooth reduce the brightness of Jovian moons for get more realistic look
2405 				if (core->getCurrentLocation().planetName=="Earth") // phase angle corrections only work for the small phase angles visible on earth.
2406 				{
2407 					if (englishName=="Io")
2408 					{
2409 						const float mag = static_cast<float>(-1.68 + d + phaseDeg*(0.046  - 0.0010 *phaseDeg));
2410 						return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2411 					}
2412 					if (englishName=="Europa")
2413 					{
2414 						const float mag = static_cast<float>(-1.41 + d + phaseDeg*(0.0312 - 0.00125*phaseDeg));
2415 						return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2416 					}
2417 					if (englishName=="Ganymede")
2418 					{
2419 						const float mag = static_cast<float>(-2.09 + d + phaseDeg*(0.0323 - 0.00066*phaseDeg));
2420 						return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2421 					}
2422 					if (englishName=="Callisto")
2423 					{
2424 						const float mag = static_cast<float>(-1.05 + d + phaseDeg*(0.078  - 0.00274*phaseDeg));
2425 						return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2426 					}
2427 					if ((!fuzzyEquals(absoluteMagnitude,-99.f)) && (englishName!="Moon"))
2428 						return absoluteMagnitude+static_cast<float>(d);
2429 				}
2430 				break;
2431 			}
2432 
2433 			case ExplanatorySupplement_2013:
2434 			{
2435 				// GZ2017: This is taken straight from the Explanatory Supplement to the Astronomical Ephemeris 2013 (chap. 10.3)
2436 				// AW2017: Updated data from Errata in The Explanatory Supplement to the Astronomical Almanac (3rd edition, 1st printing)
2437 				//         http://aa.usno.navy.mil/publications/docs/exp_supp_errata.pdf (Last update: 1 December 2016)
2438 				if (englishName=="Mercury")
2439 					return static_cast<float>(-0.6 + d + (((3.02e-6*phaseDeg - 0.000488)*phaseDeg + 0.0498)*phaseDeg));
2440 				if (englishName=="Venus")
2441 				{
2442 					// there are two regions strongly enclosed per phaseDeg (2.2..163.6..170.2). However, we must deliver a solution for every case.
2443 					// GZ: The model seems flawed. See https://sourceforge.net/p/stellarium/discussion/278769/thread/b7cab45f62/?limit=25#907d
2444 					// In this case, it seems better to deviate from the paper and --- only for the inferior conjunction --
2445 					// use a more modern value from Mallama&Hilton, https://doi.org/10.1016/j.ascom.2018.08.002
2446 					// The reversal and intermediate peak is real and due to forward scattering on sulphur acide droplets.
2447 					if (phaseDeg<163.6)
2448 						return static_cast<float>(-4.47 + d + ((0.13e-6*phaseDeg + 0.000057)*phaseDeg + 0.0103)*phaseDeg);
2449 					else
2450 						return static_cast<float>(236.05828 + d - 2.81914*phaseDeg + 8.39034E-3*phaseDeg*phaseDeg);
2451 				}
2452 				if (englishName=="Earth")
2453 					return static_cast<float>(-3.87 + d + (((0.48e-6*phaseDeg + 0.000019)*phaseDeg + 0.0130)*phaseDeg));
2454 				if (englishName=="Mars")
2455 					return static_cast<float>(-1.52 + d + 0.016*phaseDeg);
2456 				if (englishName=="Jupiter")
2457 					return static_cast<float>(-9.40 + d + 0.005*phaseDeg);
2458 				if (englishName=="Saturn")
2459 				{
2460 					// add rings computation
2461 					// implemented from Meeus, Astr.Alg.1992
2462 					const double jde=core->getJDE();
2463 					const double T=(jde-2451545.0)/36525.0;
2464 					const double i=((0.000004*T-0.012998)*T+28.075216)*M_PI/180.0;
2465 					const double Omega=((0.000412*T+1.394681)*T+169.508470)*M_PI/180.0;
2466 					static SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
2467 					const Vec3d saturnEarth=getHeliocentricEclipticPos() - ssystem->getEarth()->getHeliocentricEclipticPos();
2468 					const double lambda=atan2(saturnEarth[1], saturnEarth[0]);
2469 					const double beta=atan2(saturnEarth[2], std::sqrt(saturnEarth[0]*saturnEarth[0]+saturnEarth[1]*saturnEarth[1]));
2470 					const double sinx=sin(i)*cos(beta)*sin(lambda-Omega)-cos(i)*sin(beta);
2471 					const double ringsIllum = -2.6*fabs(sinx) + 1.25*sinx*sinx; // ExplSup2013: added term as (10.81)
2472 					return static_cast<float>(-8.88 + d + 0.044*phaseDeg + ringsIllum);
2473 				}
2474 				if (englishName=="Uranus")
2475 					return static_cast<float>(-7.19 + d + 0.002*phaseDeg);
2476 				if (englishName=="Neptune")
2477 					return static_cast<float>(-6.87 + d);
2478 				if (englishName=="Pluto")
2479 					return static_cast<float>(-1.01 + d);
2480 
2481 				// AW 2017: I've added special case for Jupiter's moons when they are in the shadow of Jupiter.
2482 				// TODO: Need experimental data to fitting to real world or the scientific paper with description of model.
2483 				// GZ 2017-09: Phase coefficients for I and III corrected, based on original publication (Stebbins&Jacobsen 1928) now.
2484 				// AW 2020-02: Let's use linear model in the first approximation for smooth reduce the brightness of Jovian moons for get more realistic look
2485 				if (englishName=="Io")
2486 				{
2487 					const float mag = static_cast<float>(-1.68 + d + phaseDeg*(0.046  - 0.0010 *phaseDeg));
2488 					return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2489 				}
2490 				if (englishName=="Europa")
2491 				{
2492 					const float mag = static_cast<float>(-1.41 + d + phaseDeg*(0.0312 - 0.00125*phaseDeg));
2493 					return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2494 				}
2495 				if (englishName=="Ganymede")
2496 				{
2497 					const float mag = static_cast<float>(-2.09 + d + phaseDeg*(0.0323 - 0.00066*phaseDeg));
2498 					return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2499 				}
2500 				if (englishName=="Callisto")
2501 				{
2502 					const float mag = static_cast<float>(-1.05 + d + phaseDeg*(0.078  - 0.00274*phaseDeg));
2503 					return shadowFactor<1.0 ? static_cast<float>(13.*(1.-shadowFactor)) + mag : mag;
2504 				}
2505 
2506 				if ((!fuzzyEquals(absoluteMagnitude,-99.f)) && (englishName!="Moon"))
2507 					return absoluteMagnitude+static_cast<float>(d);
2508 
2509 				break;
2510 			}
2511 			case ExplanatorySupplement_1992:
2512 			{
2513 				// Algorithm contributed by Pere Planesas (Observatorio Astronomico Nacional)
2514 				// GZ2016: Actually, this is taken straight from the Explanatory Supplement to the Astronomical Ephemeris 1992! (chap. 7.12)
2515 				// The value -8.88 for Saturn V(1,0) seems to be a correction of a typo, where Suppl.Astr. gives -7.19 just like for Uranus.
2516 				double f1 = phaseDeg/100.;
2517 
2518 				if (englishName=="Mercury")
2519 				{
2520 					if ( phaseDeg > 150. ) f1 = 1.5;
2521 					return static_cast<float>(-0.36 + d + 3.8*f1 - 2.73*f1*f1 + 2*f1*f1*f1);
2522 				}
2523 				if (englishName=="Venus")
2524 					return static_cast<float>(-4.29 + d + 0.09*f1 + 2.39*f1*f1 - 0.65*f1*f1*f1);
2525 				if (englishName=="Mars")
2526 					return static_cast<float>(-1.52 + d + 0.016*phaseDeg);
2527 				if (englishName=="Jupiter")
2528 					return static_cast<float>(-9.25 + d + 0.005*phaseDeg);
2529 				if (englishName=="Saturn")
2530 				{
2531 					// add rings computation
2532 					// implemented from Meeus, Astr.Alg.1992
2533 					const double jde=core->getJDE();
2534 					const double T=(jde-2451545.0)/36525.0;
2535 					const double i=((0.000004*T-0.012998)*T+28.075216)*M_PI/180.0;
2536 					const double Omega=((0.000412*T+1.394681)*T+169.508470)*M_PI/180.0;
2537 					static SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
2538 					const Vec3d saturnEarth=getHeliocentricEclipticPos() - ssystem->getEarth()->getHeliocentricEclipticPos();
2539 					const double lambda=atan2(saturnEarth[1], saturnEarth[0]);
2540 					const double beta=atan2(saturnEarth[2], std::sqrt(saturnEarth[0]*saturnEarth[0]+saturnEarth[1]*saturnEarth[1]));
2541 					const double sinx=sin(i)*cos(beta)*sin(lambda-Omega)-cos(i)*sin(beta);
2542 					const double ringsIllum = -2.6*fabs(sinx) + 1.25*sinx*sinx;
2543 					return static_cast<float>(-8.88 + d + 0.044*phaseDeg + ringsIllum);
2544 				}
2545 				if (englishName=="Uranus")
2546 					return static_cast<float>(-7.19 + d + 0.0028*phaseDeg);
2547 				if (englishName=="Neptune")
2548 					return static_cast<float>(-6.87 + d);
2549 				if (englishName=="Pluto")
2550 					return static_cast<float>(-1.01 + d + 0.041*phaseDeg);
2551 
2552 				break;
2553 			}
2554 			case Mueller_1893:
2555 			{
2556 				// (1)
2557 				// Publicationen des Astrophysikalischen Observatoriums zu Potsdam, 8, 366, 1893.
2558 				if (englishName=="Mercury")
2559 				{
2560 					double ph50=phaseDeg-50.0;
2561 					return static_cast<float>(1.16 + d + 0.02838*ph50 + 0.0001023*ph50*ph50);
2562 				}
2563 				if (englishName=="Venus")
2564 					return static_cast<float>(-4.00 + d + 0.01322*phaseDeg + 0.0000004247*phaseDeg*phaseDeg*phaseDeg);
2565 				if (englishName=="Mars")
2566 					return static_cast<float>(-1.30 + d + 0.01486*phaseDeg);
2567 				if (englishName=="Jupiter")
2568 					return static_cast<float>(-8.93 + d);
2569 				if (englishName=="Saturn")
2570 				{
2571 					// add rings computation
2572 					// implemented from Meeus, Astr.Alg.1992
2573 					const double jde=core->getJDE();
2574 					const double T=(jde-2451545.0)/36525.0;
2575 					const double i=((0.000004*T-0.012998)*T+28.075216)*M_PI/180.0;
2576 					const double Omega=((0.000412*T+1.394681)*T+169.508470)*M_PI/180.0;
2577 					SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
2578 					const Vec3d saturnEarth=getHeliocentricEclipticPos() - ssystem->getEarth()->getHeliocentricEclipticPos();
2579 					const double lambda=atan2(saturnEarth[1], saturnEarth[0]);
2580 					const double beta=atan2(saturnEarth[2], std::sqrt(saturnEarth[0]*saturnEarth[0]+saturnEarth[1]*saturnEarth[1]));
2581 					const double sinB=sin(i)*cos(beta)*sin(lambda-Omega)-cos(i)*sin(beta);
2582 					const double ringsIllum = -2.6*fabs(sinB) + 1.25*sinB*sinB; // sinx=sinB, saturnicentric latitude of earth. longish, see Meeus.
2583 					return static_cast<float>(-8.68 + d + 0.044*phaseDeg + ringsIllum);
2584 				}
2585 				if (englishName=="Uranus")
2586 					return static_cast<float>(-6.85 + d);
2587 				if (englishName=="Neptune")
2588 					return static_cast<float>(-7.05 + d);
2589 				// Original formulae doesn't have equation for Pluto
2590 				if (englishName=="Pluto")
2591 					return static_cast<float>(-1.0 + d);
2592 
2593 				break;
2594 			}
2595 			case AstronomicalAlmanac_1984:
2596 			{
2597 				// (2)
2598 				if (englishName=="Mercury")
2599 					return static_cast<float>(-0.42 + d + .038*phaseDeg - 0.000273*phaseDeg*phaseDeg + 0.000002*phaseDeg*phaseDeg*phaseDeg);
2600 				if (englishName=="Venus")
2601 					return static_cast<float>(-4.40 + d + 0.0009*phaseDeg + 0.000239*phaseDeg*phaseDeg - 0.00000065*phaseDeg*phaseDeg*phaseDeg);
2602 				if (englishName=="Mars")
2603 					return static_cast<float>(-1.52 + d + 0.016*phaseDeg);
2604 				if (englishName=="Jupiter")
2605 					return static_cast<float>(-9.40 + d + 0.005*phaseDeg);
2606 				if (englishName=="Saturn")
2607 				{
2608 					// add rings computation
2609 					// implemented from Meeus, Astr.Alg.1992
2610 					const double jde=core->getJDE();
2611 					const double T=(jde-2451545.0)/36525.0;
2612 					const double i=((0.000004*T-0.012998)*T+28.075216)*M_PI/180.0;
2613 					const double Omega=((0.000412*T+1.394681)*T+169.508470)*M_PI/180.0;
2614 					static SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
2615 					const Vec3d saturnEarth=getHeliocentricEclipticPos() - ssystem->getEarth()->getHeliocentricEclipticPos();
2616 					const double lambda=atan2(saturnEarth[1], saturnEarth[0]);
2617 					const double beta=atan2(saturnEarth[2], std::sqrt(saturnEarth[0]*saturnEarth[0]+saturnEarth[1]*saturnEarth[1]));
2618 					const double sinB=sin(i)*cos(beta)*sin(lambda-Omega)-cos(i)*sin(beta);
2619 					const double ringsIllum = -2.6*fabs(sinB) + 1.25*sinB*sinB; // sinx=sinB, saturnicentric latitude of earth. longish, see Meeus.
2620 					return static_cast<float>(-8.88 + d + 0.044*phaseDeg + ringsIllum);
2621 				}
2622 				if (englishName=="Uranus")
2623 					return static_cast<float>(-7.19 + d);
2624 				if (englishName=="Neptune")
2625 					return static_cast<float>(-6.87 + d);
2626 				if (englishName=="Pluto")
2627 					return static_cast<float>(-1.00 + d);
2628 
2629 				break;
2630 			}
2631 			case Generic:
2632 			{
2633 				// drop down to calculation of visual magnitude from phase angle and albedo of the planet
2634 				break;
2635 			}
2636 		}
2637 	}
2638 
2639 	// This formula source is unknown. But this was originally used even for the Moon!
2640 	const double p = (1.0 - phaseAngle/M_PI) * cos_chi + std::sqrt(1.0 - cos_chi*cos_chi) / M_PI;
2641 	const double F = 2.0 * static_cast<double>(albedo) * equatorialRadius * equatorialRadius * p / (3.0*observerPlanetRq*planetRq) * shadowFactor;
2642 	return -26.73f - 2.5f * static_cast<float>(log10(F));
2643 }
2644 
getAngularSize(const StelCore * core) const2645 double Planet::getAngularSize(const StelCore* core) const
2646 {
2647 	const double rad = (rings ? rings->getSize() : equatorialRadius);
2648 	return std::atan2(rad*sphereScale,getJ2000EquatorialPos(core).length()) * M_180_PI;
2649 }
2650 
2651 
getSpheroidAngularSize(const StelCore * core) const2652 double Planet::getSpheroidAngularSize(const StelCore* core) const
2653 {
2654 	return std::atan2(equatorialRadius*sphereScale,getJ2000EquatorialPos(core).length()) * M_180_PI;
2655 }
2656 
2657 //the Planet and all the related infos : name, circle etc..
draw(StelCore * core,float maxMagLabels,const QFont & planetNameFont)2658 void Planet::draw(StelCore* core, float maxMagLabels, const QFont& planetNameFont)
2659 {
2660 	if (hidden)
2661 		return;
2662 
2663 	// Exclude drawing if user set a hard limit magnitude.
2664 	if (core->getSkyDrawer()->getFlagPlanetMagnitudeLimit() && (getVMagnitude(core) > static_cast<float>(core->getSkyDrawer()->getCustomPlanetMagnitudeLimit())))
2665 	{
2666 		// Get the eclipse factor to avoid hiding the Moon during a total solar eclipse, or planets in transit over the Solar disk.
2667 		// Details: https://answers.launchpad.net/stellarium/+question/395139
2668 		if (GETSTELMODULE(SolarSystem)->getSolarEclipseFactor(core).first==1.0)
2669 			return;
2670 	}
2671 
2672 	// Try to improve speed for minor planets: test if visible at all.
2673 	// For a full catalog of NEAs (11000 objects), with this and resetting deltaJD according to distance, rendering time went 4.5fps->12fps.
2674 	// TBD: Note that taking away the asteroids at this stage breaks dim-asteroid occultation of stars!
2675 	//      Maybe make another configurable flag for those interested?
2676 	// Problematic: Early-out here of course disables the wanted hint circles for dim asteroids.
2677 	// The line makes hints for asteroids 5 magnitudes below sky limiting magnitude visible.
2678 	// If asteroid is too faint to be seen, don't bother rendering. (Massive speedup if people have hundreds of orbital elements!)
2679 	// AW: Added a special case for educational purpose to drawing orbits for the Solar System Observer
2680 	// Details: https://sourceforge.net/p/stellarium/discussion/278769/thread/4828ebe4/
2681 	if (((getVMagnitude(core)-5.0f) > core->getSkyDrawer()->getLimitMagnitude()) && pType>=Planet::isAsteroid && !core->getCurrentLocation().planetName.contains("Observer", Qt::CaseInsensitive))
2682 	{
2683 		return;
2684 	}
2685 
2686 	Mat4d mat = Mat4d::translation(eclipticPos) * rotLocalToParent;
2687 
2688 	PlanetP p = parent;
2689 	switch (re.method) {
2690 		case RotationElements::Traditional:
2691 			while (p && p->parent)
2692 			{
2693 				mat = Mat4d::translation(p->eclipticPos) * mat * p->rotLocalToParent;
2694 				p = p->parent;
2695 			}
2696 			break;
2697 		case RotationElements::WGCCRE:
2698 			while (p && p->parent)
2699 			{
2700 				mat = Mat4d::translation(p->eclipticPos) * mat;
2701 				p = p->parent;
2702 			}
2703 			break;
2704 	}
2705 	mat = Mat4d::translation(aberrationPush) * mat;
2706 
2707 	// This removed totally the Planet shaking bug!!!
2708 	StelProjector::ModelViewTranformP transfo = core->getHeliocentricEclipticModelViewTransform();
2709 	transfo->combine(mat);
2710 	if (getEnglishName() == core->getCurrentLocation().planetName)
2711 	{
2712 		// Draw the rings if we are located on a planet with rings, but not the planet itself.
2713 		if (rings)
2714 		{
2715 			draw3dModel(core, transfo, 1024, true);
2716 		}
2717 		return;
2718 	}
2719 
2720 	// Compute the 2D position and check if in the screen
2721 	const StelProjectorP prj = core->getProjection(transfo);
2722 	const double screenSz = (getAngularSize(core))*M_PI_180*static_cast<double>(prj->getPixelPerRadAtCenter());
2723 	const double viewportBufferSz= (englishName=="Sun" ? screenSz+125. : screenSz);	// enlarge if this is sun with its huge halo.
2724 	const double viewport_left = prj->getViewportPosX();
2725 	const double viewport_bottom = prj->getViewportPosY();
2726 
2727 	if ((prj->project(Vec3d(0.), screenPos)
2728 	     && screenPos[1]>viewport_bottom - viewportBufferSz && screenPos[1] < viewport_bottom + prj->getViewportHeight()+viewportBufferSz
2729 	     && screenPos[0]>viewport_left - viewportBufferSz && screenPos[0] < viewport_left + prj->getViewportWidth() + viewportBufferSz))
2730 	{
2731 		// Draw the name, and the circle if it's not too close from the body it's turning around
2732 		// this prevents name overlapping (e.g. for Jupiter's satellites)
2733 		float ang_dist = 300.f*static_cast<float>(atan(getEclipticPos().length()/getEquinoxEquatorialPos(core).length())/core->getMovementMgr()->getCurrentFov());
2734 		if (ang_dist==0.f)
2735 			ang_dist = 1.f; // if ang_dist == 0, the Planet is sun..
2736 
2737 		// by putting here, only draw orbit if Planet is visible for clarity
2738 		drawOrbit(core);  // TODO - fade in here also...
2739 
2740 		if (flagLabels && ang_dist>0.25f && maxMagLabels>getVMagnitudeWithExtinction(core))
2741 			labelsFader=true;
2742 		else
2743 			labelsFader=false;
2744 		drawHints(core, planetNameFont);
2745 
2746 		draw3dModel(core,transfo,static_cast<float>(screenSz));
2747 	}
2748 	else if (permanentDrawingOrbits) // A special case for demos
2749 		drawOrbit(core);
2750 
2751 	return;
2752 }
2753 
2754 class StelPainterLight
2755 {
2756 public:
2757 	Vec3d position;
2758 	Vec3f diffuse;
2759 	Vec3f ambient;
2760 };
2761 static StelPainterLight light;
2762 
initLocations(QOpenGLShaderProgram * p)2763 void Planet::PlanetShaderVars::initLocations(QOpenGLShaderProgram* p)
2764 {
2765 	GL(p->bind());
2766 	//attributes
2767 	GL(texCoord = p->attributeLocation("texCoord"));
2768 	GL(unprojectedVertex = p->attributeLocation("unprojectedVertex"));
2769 	GL(vertex = p->attributeLocation("vertex"));
2770 	GL(normalIn = p->attributeLocation("normalIn"));
2771 
2772 	//common uniforms
2773 	GL(projectionMatrix = p->uniformLocation("projectionMatrix"));
2774 	GL(tex = p->uniformLocation("tex"));
2775 	GL(lightDirection = p->uniformLocation("lightDirection"));
2776 	GL(eyeDirection = p->uniformLocation("eyeDirection"));
2777 	GL(diffuseLight = p->uniformLocation("diffuseLight"));
2778 	GL(ambientLight = p->uniformLocation("ambientLight"));
2779 	GL(shadowCount = p->uniformLocation("shadowCount"));
2780 	GL(shadowData = p->uniformLocation("shadowData"));
2781 	GL(sunInfo = p->uniformLocation("sunInfo"));
2782 	GL(skyBrightness = p->uniformLocation("skyBrightness"));
2783 	GL(orenNayarParameters = p->uniformLocation("orenNayarParameters"));
2784 	GL(outgasParameters = p->uniformLocation("outgasParameters"));
2785 
2786 	// Moon-specific variables
2787 	GL(earthShadow = p->uniformLocation("earthShadow"));
2788 	GL(eclipsePush = p->uniformLocation("eclipsePush"));
2789 	GL(normalMap = p->uniformLocation("normalMap"));
2790 
2791 	// Rings-specific variables
2792 	GL(isRing = p->uniformLocation("isRing"));
2793 	GL(ring = p->uniformLocation("ring"));
2794 	GL(outerRadius = p->uniformLocation("outerRadius"));
2795 	GL(innerRadius = p->uniformLocation("innerRadius"));
2796 	GL(ringS = p->uniformLocation("ringS"));
2797 
2798 	// Shadowmap variables
2799 	GL(shadowMatrix = p->uniformLocation("shadowMatrix"));
2800 	GL(shadowTex = p->uniformLocation("shadowTex"));
2801 	GL(poissonDisk = p->uniformLocation("poissonDisk"));
2802 
2803 	GL(p->release());
2804 }
2805 
createShader(const QString & name,PlanetShaderVars & vars,const QByteArray & vSrc,const QByteArray & fSrc,const QByteArray & prefix,const QMap<QByteArray,int> & fixedAttributeLocations)2806 QOpenGLShaderProgram* Planet::createShader(const QString& name, PlanetShaderVars& vars, const QByteArray& vSrc, const QByteArray& fSrc, const QByteArray& prefix, const QMap<QByteArray, int> &fixedAttributeLocations)
2807 {
2808 	QOpenGLShaderProgram* program = new QOpenGLShaderProgram();
2809 	if(!program->create())
2810 	{
2811 		qCritical()<<"Planet: Cannot create shader program object for"<<name;
2812 		delete program;
2813 		return Q_NULLPTR;
2814 	}
2815 
2816 	// We HAVE to create QOpenGLShader on the heap (with automatic QObject memory management), or the shader may be destroyed before the program has been linked
2817 	// This was FUN to debug - OGL simply accepts an empty shader, no errors generated but funny colors drawn :)
2818 	// We could also use QOpenGLShaderProgram::addShaderFromSourceCode directly, but this would prevent having access to warnings (because log is copied only on errors there...)
2819 	if(!vSrc.isEmpty())
2820 	{
2821 		QOpenGLShader* shd = new QOpenGLShader(QOpenGLShader::Vertex, program);
2822 		bool ok = shd->compileSourceCode(prefix + vSrc);
2823 		QString log = shd->log();
2824 		if (!log.isEmpty() && !log.contains("no warnings", Qt::CaseInsensitive)) { qWarning() << "Planet: Warnings/Errors while compiling" << name << "vertex shader: " << log; }
2825 		if(!ok)
2826 		{
2827 			qCritical()<<name<<"vertex shader could not be compiled";
2828 			delete program;
2829 			return Q_NULLPTR;
2830 		}
2831 		if(!program->addShader(shd))
2832 		{
2833 			qCritical()<<name<<"vertex shader could not be added to program";
2834 			delete program;
2835 			return Q_NULLPTR;
2836 		}
2837 	}
2838 
2839 	if(!fSrc.isEmpty())
2840 	{
2841 		QOpenGLShader* shd = new QOpenGLShader(QOpenGLShader::Fragment, program);
2842 		bool ok = shd->compileSourceCode(prefix + fSrc);
2843 		QString log = shd->log();
2844 		if (!log.isEmpty() && !log.contains("no warnings", Qt::CaseInsensitive)) { qWarning() << "Planet: Warnings/Errors while compiling" << name << "fragment shader: " << log; }
2845 		if(!ok)
2846 		{
2847 			qCritical()<<name<<"fragment shader could not be compiled";
2848 			delete program;
2849 			return Q_NULLPTR;
2850 		}
2851 		if(!program->addShader(shd))
2852 		{
2853 			qCritical()<<name<<"fragment shader could not be added to program";
2854 			delete program;
2855 			return Q_NULLPTR;
2856 		}
2857 	}
2858 
2859 	//process fixed attribute locations
2860 	for (auto it = fixedAttributeLocations.begin(); it != fixedAttributeLocations.end(); ++it)
2861 	{
2862 		program->bindAttributeLocation(it.key(),it.value());
2863 	}
2864 
2865 	if(!StelPainter::linkProg(program,name))
2866 	{
2867 		delete program;
2868 		return Q_NULLPTR;
2869 	}
2870 
2871 	vars.initLocations(program);
2872 
2873 	return program;
2874 }
2875 
initShader()2876 bool Planet::initShader()
2877 {
2878 	if (planetShaderProgram || shaderError) return !shaderError; // Already done.
2879 	qDebug() << "Initializing planets GL shaders... ";
2880 	shaderError = true;
2881 
2882 	QSettings* settings = StelApp::getInstance().getSettings();
2883 	settings->sync();
2884 	shadowPolyOffset = Vec2f(settings->value("astro/planet_shadow_polygonoffset", Vec2f(0.0f, 0.0f).toStr()).toString());
2885 	//qDebug()<<"Shadow poly offset"<<shadowPolyOffset;
2886 
2887 	// Shader text is loaded from file
2888 	QString vFileName = StelFileMgr::findFile("data/shaders/planet.vert",StelFileMgr::File);
2889 	QString fFileName = StelFileMgr::findFile("data/shaders/planet.frag",StelFileMgr::File);
2890 
2891 	if(vFileName.isEmpty())
2892 	{
2893 		qCritical()<<"Cannot find 'data/shaders/planet.vert', can't use planet rendering!";
2894 		return false;
2895 	}
2896 	if(fFileName.isEmpty())
2897 	{
2898 		qCritical()<<"Cannot find 'data/shaders/planet.frag', can't use planet rendering!";
2899 		return false;
2900 	}
2901 
2902 	QFile vFile(vFileName);
2903 	QFile fFile(fFileName);
2904 
2905 	if(!vFile.open(QIODevice::ReadOnly | QIODevice::Text))
2906 	{
2907 		qCritical()<<"Cannot load planet vertex shader file"<<vFileName<<vFile.errorString();
2908 		return false;
2909 	}
2910 	QByteArray vsrc = vFile.readAll();
2911 	vFile.close();
2912 
2913 	if(!fFile.open(QIODevice::ReadOnly | QIODevice::Text))
2914 	{
2915 		qCritical()<<"Cannot load planet fragment shader file"<<fFileName<<fFile.errorString();
2916 		return false;
2917 	}
2918 	QByteArray fsrc = fFile.readAll();
2919 	fFile.close();
2920 
2921 	shaderError = false;
2922 
2923 	// Default planet shader program
2924 	planetShaderProgram = createShader("planetShaderProgram",planetShaderVars,vsrc,fsrc);
2925 	// Planet with ring shader program
2926 	ringPlanetShaderProgram = createShader("ringPlanetShaderProgram",ringPlanetShaderVars,vsrc,fsrc,"#define RINGS_SUPPORT\n\n");
2927 	// Moon shader program
2928 	moonShaderProgram = createShader("moonShaderProgram",moonShaderVars,vsrc,fsrc,"#define IS_MOON\n\n");
2929 	// OBJ model shader program
2930 	// we REQUIRE some fixed attribute locations here
2931 	QMap<QByteArray,int> attrLoc;
2932 	attrLoc.insert("unprojectedVertex", StelOpenGLArray::ATTLOC_VERTEX);
2933 	attrLoc.insert("texCoord", StelOpenGLArray::ATTLOC_TEXCOORD);
2934 	attrLoc.insert("normalIn", StelOpenGLArray::ATTLOC_NORMAL);
2935 	objShaderProgram = createShader("objShaderProgram",objShaderVars,vsrc,fsrc,"#define IS_OBJ\n\n",attrLoc);
2936 	//OBJ shader with shadowmap support
2937 	objShadowShaderProgram = createShader("objShadowShaderProgram",objShadowShaderVars,vsrc,fsrc,
2938 					      "#define IS_OBJ\n"
2939 					      "#define SHADOWMAP\n"
2940 					      "#define SM_SIZE " STRINGIFY(SM_SIZE) "\n"
2941 										    "\n",attrLoc);
2942 
2943 	//set the poisson disk as uniform, this seems to be the only way to get an (const) array into GLSL 110 on all drivers
2944 	if(objShadowShaderProgram)
2945 	{
2946 		objShadowShaderProgram->bind();
2947 		const float poissonDisk[] ={
2948 			-0.610470f, -0.702763f,
2949 			 0.609267f,  0.765488f,
2950 			-0.817537f, -0.412950f,
2951 			 0.777710f, -0.446717f,
2952 			-0.668764f, -0.524195f,
2953 			 0.425181f,  0.797780f,
2954 			-0.766728f, -0.065185f,
2955 			 0.266692f,  0.917346f,
2956 			-0.578028f, -0.268598f,
2957 			 0.963767f,  0.079058f,
2958 			-0.968971f, -0.039291f,
2959 			 0.174263f, -0.141862f,
2960 			-0.348933f, -0.505110f,
2961 			 0.837686f, -0.083142f,
2962 			-0.462722f, -0.072878f,
2963 			 0.701887f, -0.281632f,
2964 			-0.377209f, -0.247278f,
2965 			 0.765589f,  0.642157f,
2966 			-0.678950f,  0.128138f,
2967 			 0.418512f, -0.186050f,
2968 			-0.442419f,  0.242444f,
2969 			 0.442748f, -0.456745f,
2970 			-0.196461f,  0.084314f,
2971 			 0.536558f, -0.770240f,
2972 			-0.190154f, -0.268138f,
2973 			 0.643032f, -0.584872f,
2974 			-0.160193f, -0.457076f,
2975 			 0.089220f,  0.855679f,
2976 			-0.200650f, -0.639838f,
2977 			 0.220825f,  0.710969f,
2978 			-0.330313f, -0.812004f,
2979 			-0.046886f,  0.721859f,
2980 			 0.070102f, -0.703208f,
2981 			-0.161384f,  0.952897f,
2982 			 0.034711f, -0.432054f,
2983 			-0.508314f,  0.638471f,
2984 			-0.026992f, -0.163261f,
2985 			 0.702982f,  0.089288f,
2986 			-0.004114f, -0.901428f,
2987 			 0.656819f,  0.387131f,
2988 			-0.844164f,  0.526829f,
2989 			 0.843124f,  0.220030f,
2990 			-0.802066f,  0.294509f,
2991 			 0.863563f,  0.399832f,
2992 			 0.268762f, -0.576295f,
2993 			 0.465623f,  0.517930f,
2994 			 0.340116f, -0.747385f,
2995 			 0.223493f,  0.516709f,
2996 			 0.240980f, -0.942373f,
2997 			-0.689804f,  0.649927f,
2998 			 0.272309f, -0.297217f,
2999 			 0.378957f,  0.162593f,
3000 			 0.061461f,  0.067313f,
3001 			 0.536957f,  0.249192f,
3002 			-0.252331f,  0.265096f,
3003 			 0.587532f, -0.055223f,
3004 			 0.034467f,  0.289122f,
3005 			 0.215271f,  0.278700f,
3006 			-0.278059f,  0.615201f,
3007 			-0.369530f,  0.791952f,
3008 			-0.026918f,  0.542170f,
3009 			 0.274033f,  0.010652f,
3010 			-0.561495f,  0.396310f,
3011 			-0.367752f,  0.454260f
3012 		};
3013 		objShadowShaderProgram->setUniformValueArray(objShadowShaderVars.poissonDisk,poissonDisk,64,2);
3014 		objShadowShaderProgram->release();
3015 	}
3016 
3017 	//this is a simple transform-only shader (used for filling the depth map for OBJ shadows)
3018 	QByteArray transformVShader(
3019 				"uniform mat4 projectionMatrix;\n"
3020 				"attribute vec4 unprojectedVertex;\n"
3021 			#ifdef DEBUG_SHADOWMAP
3022 				"attribute mediump vec2 texCoord;\n"
3023 				"varying mediump vec2 texc; //texture coord\n"
3024 				"varying highp vec4 pos; //projected pos\n"
3025 			#endif
3026 				"void main()\n"
3027 				"{\n"
3028 			#ifdef DEBUG_SHADOWMAP
3029 				"   texc = texCoord;\n"
3030 				"   pos = projectionMatrix * unprojectedVertex;\n"
3031 			#endif
3032 				"   gl_Position = projectionMatrix * unprojectedVertex;\n"
3033 				"}\n"
3034 				);
3035 
3036 #ifdef DEBUG_SHADOWMAP
3037 	const QByteArray transformFShader(
3038 				"uniform lowp sampler2D tex;\n"
3039 				"varying mediump vec2 texc; //texture coord\n"
3040 				"varying highp vec4 pos; //projected pos\n"
3041 				"void main()\n"
3042 				"{\n"
3043 				"   lowp vec4 texCol = texture2D(tex,texc);\n"
3044 				"   highp float zNorm = (pos.z + 1.0) / 2.0;\n" //from [-1,1] to [0,1]
3045 				"   gl_FragColor = vec4(texCol.rgb,zNorm);\n"
3046 				"}\n"
3047 				);
3048 #else
3049 	//On ES2, we have to create an empty dummy FShader or the compiler may complain
3050 	//but don't do this on desktop, at least my Intel fails linking with this (with no log message, yay...)
3051 	QByteArray transformFShader;
3052 	if(QOpenGLContext::currentContext()->isOpenGLES())
3053 	{
3054 		transformFShader = "void main()\n{ }\n";
3055 	}
3056 #endif
3057 	GL(transformShaderProgram = createShader("transformShaderProgram", transformShaderVars, transformVShader, transformFShader,QByteArray(),attrLoc));
3058 
3059 	//check if ALL shaders have been created correctly
3060 	shaderError = !(planetShaderProgram&&
3061 			ringPlanetShaderProgram&&
3062 			moonShaderProgram&&
3063 			objShaderProgram&&
3064 			objShadowShaderProgram&&
3065 			transformShaderProgram);
3066 	return true;
3067 }
3068 
deinitShader()3069 void Planet::deinitShader()
3070 {
3071 	//note that it is not necessary to check for Q_NULLPTR before delete
3072 	delete planetShaderProgram;
3073 	planetShaderProgram = Q_NULLPTR;
3074 	delete ringPlanetShaderProgram;
3075 	ringPlanetShaderProgram = Q_NULLPTR;
3076 	delete moonShaderProgram;
3077 	moonShaderProgram = Q_NULLPTR;
3078 	delete objShaderProgram;
3079 	objShaderProgram = Q_NULLPTR;
3080 	delete objShadowShaderProgram;
3081 	objShadowShaderProgram = Q_NULLPTR;
3082 	delete transformShaderProgram;
3083 	transformShaderProgram = Q_NULLPTR;
3084 }
3085 
initFBO()3086 bool Planet::initFBO()
3087 {
3088 	if(shadowInitialized)
3089 		return false;
3090 
3091 	QOpenGLContext* ctx  = QOpenGLContext::currentContext();
3092 	QOpenGLFunctions* gl = ctx->functions();
3093 
3094 	bool isGLESv2 = false;
3095 	bool error = false;
3096 	//check if support for the required features is available
3097 	if(!ctx->functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers))
3098 	{
3099 		qWarning()<<"Your GL driver does not support framebuffer objects, OBJ model self-shadows will not be available";
3100 		error = true;
3101 	}
3102 	else if(ctx->isOpenGLES() &&
3103 			ctx->format().majorVersion()<3)
3104 	{
3105 		isGLESv2 = true;
3106 		//GLES v2 requires extensions for depth textures
3107 		if(!(ctx->hasExtension("GL_OES_depth_texture") || ctx->hasExtension("GL_ANGLE_depth_texture")))
3108 		{
3109 			qWarning()<<"Your GL driver has no support for depth textures, OBJ model self-shadows will not be available";
3110 			error = true;
3111 		}
3112 	}
3113 	//on desktop, depth textures should be available on all GL >= 1.4 contexts, so no check should be required
3114 
3115 	if(!error)
3116 	{
3117 		//all seems ok, create our objects
3118 		GL(gl->glGenTextures(1, &shadowTex));
3119 		GL(gl->glActiveTexture(GL_TEXTURE1));
3120 		GL(gl->glBindTexture(GL_TEXTURE_2D, shadowTex));
3121 
3122 #ifndef QT_OPENGL_ES_2
3123 		if(!isGLESv2)
3124 		{
3125 			GL(gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0));
3126 			GL(gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
3127 			GL(gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER));
3128 			GL(gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER));
3129 			const float ones[] = {1.0f, 1.0f, 1.0f, 1.0f};
3130 			GL(gl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, ones));
3131 		}
3132 #endif
3133 		GL(gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
3134 		GL(gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
3135 
3136 #ifndef DEBUG_SHADOWMAP
3137 		//create the texture
3138 		//note that the 'type' must be GL_UNSIGNED_SHORT or GL_UNSIGNED_INT for ES2 compatibility, even if the 'pixels' are Q_NULLPTR
3139 		GL(gl->glTexImage2D(GL_TEXTURE_2D, 0, isGLESv2?GL_DEPTH_COMPONENT:GL_DEPTH_COMPONENT16, SM_SIZE, SM_SIZE, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, Q_NULLPTR));
3140 
3141 		//we dont use QOpenGLFramebuffer because we dont want a color buffer...
3142 		GL(gl->glGenFramebuffers(1, &shadowFBO));
3143 		GL(gl->glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO));
3144 
3145 		//attach shadow tex to FBO
3146 		gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowTex, 0);
3147 
3148 		//on desktop, we must disable the read/draw buffer because we have no color buffer
3149 		//else, it would be an FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER error
3150 		//see GL_EXT_framebuffer_object and GL_ARB_framebuffer_object
3151 		//on ES 2, this seems to be allowed (there are no glDrawBuffers/glReadBuffer functions there), see GLES spec section 4.4.4
3152 		//probably same on ES 3: though it has glDrawBuffers/glReadBuffer but no mention of it in section 4.4.4 and no FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER is defined
3153 #ifndef QT_OPENGL_ES_2
3154 		if(!ctx->isOpenGLES())
3155 		{
3156 			QOpenGLFunctions_1_0* gl10 = ctx->versionFunctions<QOpenGLFunctions_1_0>();
3157 			if(Q_LIKELY(gl10))
3158 			{
3159 				//use DrawBuffer instead of DrawBuffers
3160 				//because it is available since GL 1.0 instead of only on 3+
3161 				gl10->glDrawBuffer(GL_NONE);
3162 				gl10->glReadBuffer(GL_NONE);
3163 			}
3164 			else
3165 			{
3166 				//something is probably not how we want it
3167 				Q_ASSERT(0);
3168 			}
3169 		}
3170 #endif
3171 
3172 		//check for completeness
3173 		GLenum status = gl->glCheckFramebufferStatus(GL_FRAMEBUFFER);
3174 		if(status != GL_FRAMEBUFFER_COMPLETE)
3175 		{
3176 			error = true;
3177 			qWarning()<<"Planet self-shadow framebuffer is incomplete, cannot use. Status:"<<status;
3178 		}
3179 
3180 		GL(gl->glBindFramebuffer(GL_FRAMEBUFFER, StelApp::getInstance().getDefaultFBO()));
3181 		gl->glActiveTexture(GL_TEXTURE0);
3182 #else
3183 		GL(shadowFBO = new QOpenGLFramebufferObject(SM_SIZE,SM_SIZE,QOpenGLFramebufferObject::Depth));
3184 		error = !shadowFBO->isValid();
3185 #endif
3186 
3187 		qDebug()<<"Planet self-shadow framebuffer initialized";
3188 	}
3189 
3190 	shadowInitialized = true;
3191 	return !error;
3192 }
3193 
deinitFBO()3194 void Planet::deinitFBO()
3195 {
3196 	if(!shadowInitialized)
3197 		return;
3198 
3199 	QOpenGLFunctions* gl = QOpenGLContext::currentContext()->functions();
3200 
3201 #ifndef DEBUG_SHADOWMAP
3202 	//zeroed names are ignored by GL
3203 	gl->glDeleteFramebuffers(1,&shadowFBO);
3204 	shadowFBO = 0;
3205 #else
3206 	delete shadowFBO;
3207 	shadowFBO = Q_NULLPTR;
3208 #endif
3209 	gl->glDeleteTextures(1,&shadowTex);
3210 	shadowTex = 0;
3211 
3212 	shadowInitialized = false;
3213 }
3214 
draw3dModel(StelCore * core,StelProjector::ModelViewTranformP transfo,float screenSz,bool drawOnlyRing)3215 void Planet::draw3dModel(StelCore* core, StelProjector::ModelViewTranformP transfo, float screenSz, bool drawOnlyRing)
3216 {
3217 	// This is the main method drawing a planet 3d model
3218 	// Some work has to be done on this method to make the rendering nicer
3219 	SolarSystem* ssm = GETSTELMODULE(SolarSystem);
3220 
3221 	// Find extinction settings to change colors. The method is rather ad-hoc.
3222 	const float extinctedMag=getVMagnitudeWithExtinction(core)-getVMagnitude(core); // this is net value of extinction, in mag.
3223 	const float magFactorGreen=powf(0.85f, 0.6f*extinctedMag);
3224 	const float magFactorBlue=powf(0.6f, 0.5f*extinctedMag);
3225 
3226 	if (screenSz>1.f)
3227 	{
3228 		//make better use of depth buffer by adjusting clipping planes
3229 		//must be done before creating StelPainter
3230 		//depth buffer is used for object with rings or with OBJ models
3231 		//it is not used for normal spherical object rendering without rings!
3232 		//but it does not hurt to adjust it in all cases
3233 		double n,f;
3234 		core->getClippingPlanes(&n,&f); // Save clipping planes
3235 
3236 		//determine the minimum size of the clip space
3237 		double r = equatorialRadius*sphereScale;
3238 		if(rings)
3239 			r+=rings->getSize()*sphereScale;
3240 
3241 		const double dist = getEquinoxEquatorialPos(core).length();
3242 		const double z_near = qMax(0.00001, (dist - r)); //near Z should be as close as possible to the actual geometry
3243 		const double z_far  = (dist + 10*r); //far Z should be quite a bit further behind (Z buffer accuracy is worse near the far plane)
3244 		core->setClippingPlanes(z_near,z_far);
3245 
3246 		StelProjector::ModelViewTranformP transfo2 = transfo->clone();
3247 		transfo2->combine(Mat4d::zrotation(M_PI_180*static_cast<double>(axisRotation + 90.f)));
3248 		StelPainter sPainter(core->getProjection(transfo2));
3249 		gl = sPainter.glFuncs();
3250 
3251 		#ifdef GL_MULTISAMPLE
3252 		if(multisamplingEnabled_)
3253 			gl->glEnable(GL_MULTISAMPLE);
3254 		else
3255 			gl->glDisable(GL_MULTISAMPLE);
3256 		#endif
3257 
3258 		// Set the main source of light to be the sun.
3259 		// This must be the aberrated sun! (Mostly theoretically, this displacement seems more important for the shadows, done elsewhere...)
3260 		Vec3d sunPos = ssm->getSun()->getEclipticPos() + ssm->getSun()->getAberrationPush();
3261 		core->getHeliocentricEclipticModelViewTransform()->forward(sunPos);
3262 		light.position=sunPos;
3263 
3264 		// Set the light parameters taking sun as the light source
3265 		light.diffuse.set(1.f,  magFactorGreen*1.f,  magFactorBlue*1.f);
3266 		light.ambient.set(0.02f,magFactorGreen*0.02f,magFactorBlue*0.02f);
3267 
3268 		if (this==ssm->getMoon())
3269 		{
3270 			// ambient for the moon can provide the Ashen light!
3271 			// during daylight, this still should not make moon visible. We grab sky brightness and dim the moon.
3272 			// This approach here is again pretty ad-hoc.
3273 			// We have 5000cd/m^2 at sunset returned (Note this may be unnaturally much. Should be rather 10, but the 5000 may include the sun).
3274 			// When atm.brightness has fallen to 2000cd/m^2, we allow ashen light to appear visible. Its impact is full when atm.brightness is below 1000.
3275 			LandscapeMgr* lmgr = GETSTELMODULE(LandscapeMgr);
3276 			Q_ASSERT(lmgr);
3277 			const float atmLum=(lmgr->getFlagAtmosphere() ? lmgr->getAtmosphereAverageLuminance() : 0.0f);
3278 			if (atmLum<2000.0f)
3279 			{
3280 				float atmScaling=1.0f - (qMax(1000.0f, atmLum)-1000.0f)*0.001f; // full impact when atmLum<1000.
3281 				float ashenFactor=(1.0f-getPhase(ssm->getEarth()->getHeliocentricEclipticPos())); // We really mean the Earth for this! (Try observing from Mars ;-)
3282 				ashenFactor*=ashenFactor*0.15f*atmScaling;
3283 				light.ambient = Vec4f(ashenFactor, magFactorGreen*ashenFactor, magFactorBlue*ashenFactor);
3284 			}
3285 			const float fov=core->getProjection(transfo)->getFov();
3286 			float fovFactor=1.6f;
3287 			// scale brightness to reduce if fov smaller than 5 degrees. Min brightness (to avoid glare) if fov=2deg.
3288 			if (fov<5.0f)
3289 			{
3290 				fovFactor -= 0.1f*(5.0f-qMax(2.0f, fov));
3291 			}
3292 			// Special case for the Moon. Was 1.6, but this often is too bright.
3293 			light.diffuse.set(fovFactor,magFactorGreen*fovFactor,magFactorBlue*fovFactor);
3294 		}
3295 
3296 		// possibly tint sun's color from extinction. This should deliberately cause stronger reddening than for the other objects.
3297 		if (this==ssm->getSun())
3298 		{
3299 			// when we zoom in, reduce the overbrightness. (LP:#1421173)
3300 			const float fov=core->getProjection(transfo)->getFov();
3301 			const float overbright=qBound(0.85f, 0.5f*fov, 2.0f); // scale full brightness to 0.85...2. (<2 when fov gets under 4 degrees)
3302 			sPainter.setColor(overbright, powf(0.75f, extinctedMag)*overbright, powf(0.42f, 0.9f*extinctedMag)*overbright);
3303 		}
3304 
3305 		//if (rings) /// GZ This was the previous condition. Not sure why rings were dropped?
3306 		if(ssm->getFlagUseObjModels() && !objModelPath.isEmpty())
3307 		{
3308 			if(!drawObjModel(&sPainter, screenSz))
3309 			{
3310 				drawSphere(&sPainter, screenSz, drawOnlyRing);
3311 			}
3312 		}
3313 		else if (!survey || survey->getInterstate() < 1.0f)
3314 		{
3315 			drawSphere(&sPainter, screenSz, drawOnlyRing);
3316 		}
3317 
3318 		if (survey && survey->getInterstate() > 0.0f)
3319 		{
3320 			drawSurvey(core, &sPainter);
3321 			drawSphere(&sPainter, screenSz, true);
3322 		}
3323 
3324 
3325 		core->setClippingPlanes(n,f);  // Restore old clipping planes
3326 		#ifdef GL_MULTISAMPLE
3327 		if(multisamplingEnabled_)
3328 			gl->glDisable(GL_MULTISAMPLE);
3329 		#endif
3330 	}
3331 
3332 	bool allowDrawHalo = true;
3333 	if ((this!=ssm->getSun()) && ((this !=ssm->getMoon() && core->getCurrentLocation().planetName=="Earth" )))
3334 	{
3335 		// Let's hide halo when inner planet between Sun and observer (or moon between planet and observer).
3336 		// Do not hide Earth's moon's halo below ~-45degrees when observing from earth.
3337 		const Vec3d obj = getJ2000EquatorialPos(core);
3338 		const Vec3d par = getParent()->getJ2000EquatorialPos(core);
3339 		const double angle = obj.angle(par)*M_180_PI;
3340 		const double asize = getParent()->getSpheroidAngularSize(core);
3341 		if (angle<=asize)
3342 			allowDrawHalo = false;
3343 	}
3344 
3345 	if (this==ssm->getMoon() && !drawMoonHalo)
3346 		allowDrawHalo = false;
3347 
3348 	// Draw the halo if enabled in the ssystem_*.ini files (+ special case for backward compatible for the Sun)
3349 	if ((hasHalo() || this==ssm->getSun()) && allowDrawHalo)
3350 	{
3351 		// Prepare OpenGL lighting parameters according to luminance. For scaled-up planets, reduce brightness of the halo.
3352 		float surfArcMin2 = static_cast<float>(getSpheroidAngularSize(core)*qMax(1.0, (englishName=="Moon" ? 1.0 : 0.025)*sphereScale))*60.f;
3353 		surfArcMin2 = surfArcMin2*surfArcMin2*M_PIf; // the total illuminated area in arcmin^2
3354 
3355 		StelPainter sPainter(core->getProjection(StelCore::FrameJ2000));
3356 		Vec3d tmp = getJ2000EquatorialPos(core);
3357 
3358 		// Find new extincted color for halo. The method is again rather ad-hoc, but does not look too bad.
3359 		// For the sun, we have again to use the stronger extinction to avoid color mismatch.
3360 		Vec3f haloColorToDraw;
3361 		if (this==ssm->getSun())
3362 			haloColorToDraw.set(haloColor[0], powf(0.75f, extinctedMag) * haloColor[1], powf(0.42f, 0.9f*extinctedMag) * haloColor[2]);
3363 		else
3364 			haloColorToDraw.set(haloColor[0], magFactorGreen * haloColor[1], magFactorBlue * haloColor[2]);
3365 		if (this==ssm->getMoon())
3366 			haloColorToDraw*=0.6f; // make lunar halo less glaring, so that phase is discernible even if zoomed out.
3367 
3368 		if (this!=ssm->getSun() || drawSunHalo)
3369 			core->getSkyDrawer()->postDrawSky3dModel(&sPainter, tmp.toVec3f(), surfArcMin2, getVMagnitudeWithExtinction(core), haloColorToDraw);
3370 
3371 		if ((this==ssm->getSun()) && (core->getCurrentLocation().planetName == "Earth"))
3372 		{
3373 			LandscapeMgr* lmgr = GETSTELMODULE(LandscapeMgr);
3374 			const float eclipseFactor = static_cast<float>(ssm->getSolarEclipseFactor(core).first);
3375 			// This alpha ensures 0 for complete sun, 1 for eclipse better 1e-10, with a strong increase towards full eclipse. We still need to square it.
3376 			// But without atmosphere we should indeed draw a visible corona by default!
3377 			const float alpha= ( !lmgr->getFlagAtmosphere() && ssm->getFlagPermanentSolarCorona() ? 0.7f : -0.1f*qMax(-10.0f, log10f(eclipseFactor)));
3378 			StelMovementMgr* mmgr = GETSTELMODULE(StelMovementMgr);
3379 			float rotationAngle=(mmgr->getEquatorialMount() ? 0.0f : getParallacticAngle(core) * M_180_PIf);
3380 
3381 			// Add ecliptic/equator angle. Meeus, Astr. Alg. 2nd, p100.
3382 			const double jde=core->getJDE();
3383 			const double eclJDE = GETSTELMODULE(SolarSystem)->getEarth()->getRotObliquity(jde);
3384 			double ra_equ, dec_equ, lambdaJDE, betaJDE;
3385 			StelUtils::rectToSphe(&ra_equ,&dec_equ,getEquinoxEquatorialPos(core));
3386 			StelUtils::equToEcl(ra_equ, dec_equ, eclJDE, &lambdaJDE, &betaJDE);
3387 			// We can safely assume beta=0 and ignore nutation.
3388 			const float q0=static_cast<float>(atan(-cos(lambdaJDE)*tan(eclJDE)));
3389 			rotationAngle -= q0*static_cast<float>(180.0/M_PI);
3390 
3391 			core->getSkyDrawer()->drawSunCorona(&sPainter, tmp.toVec3f(), 512.f/192.f*screenSz, haloColorToDraw, alpha*alpha, rotationAngle);
3392 		}
3393 	}
3394 }
3395 
3396 struct Planet3DModel
3397 {
3398 	QVector<float> vertexArr;
3399 	QVector<float> texCoordArr;
3400 	QVector<unsigned short> indiceArr;
3401 };
3402 
3403 
sSphere(Planet3DModel * model,const float radius,const float oneMinusOblateness,const unsigned short int slices,const unsigned short int stacks)3404 void sSphere(Planet3DModel* model, const float radius, const float oneMinusOblateness, const unsigned short int slices, const unsigned short int stacks)
3405 {
3406 	model->indiceArr.resize(0);
3407 	model->vertexArr.resize(0);
3408 	model->texCoordArr.resize(0);
3409 
3410 	GLfloat x, y, z;
3411 	GLfloat s=0.f, t=1.f;
3412 	GLushort i, j;
3413 
3414 	const float* cos_sin_rho = StelUtils::ComputeCosSinRho(stacks);
3415 	const float* cos_sin_theta =  StelUtils::ComputeCosSinTheta(slices);
3416 
3417 	const float* cos_sin_rho_p;
3418 	const float *cos_sin_theta_p;
3419 
3420 	// texturing: s goes from 0.0/0.25/0.5/0.75/1.0 at +y/+x/-y/-x/+y axis
3421 	// t goes from 0.0/+1.0 at z = -radius/+radius (linear along longitudes)
3422 	// cannot use triangle fan on texturing (s coord. at top/bottom tip varies)
3423 	// If the texture is flipped, we iterate the coordinates backward.
3424 	const GLfloat ds = 1.f / slices;
3425 	const GLfloat dt = 1.f / stacks; // from inside texture is reversed
3426 
3427 	// draw intermediate  as quad strips
3428 	for (i = 0,cos_sin_rho_p = cos_sin_rho; i < stacks; ++i,cos_sin_rho_p+=2)
3429 	{
3430 		s = 0.f;
3431 		for (j = 0,cos_sin_theta_p = cos_sin_theta; j<=slices;++j,cos_sin_theta_p+=2)
3432 		{
3433 			x = -cos_sin_theta_p[1] * cos_sin_rho_p[1];
3434 			y = cos_sin_theta_p[0] * cos_sin_rho_p[1];
3435 			z = cos_sin_rho_p[0];
3436 			model->texCoordArr << s << t;
3437 			model->vertexArr << x * radius << y * radius << z * oneMinusOblateness * radius;
3438 			x = -cos_sin_theta_p[1] * cos_sin_rho_p[3];
3439 			y = cos_sin_theta_p[0] * cos_sin_rho_p[3];
3440 			z = cos_sin_rho_p[2];
3441 			model->texCoordArr << s << t - dt;
3442 			model->vertexArr << x * radius << y * radius << z * oneMinusOblateness * radius;
3443 			s += ds;
3444 		}
3445 		unsigned short int offset = i*(slices+1)*2;
3446 		unsigned short int limit = slices*2u+2u;
3447 		for (j = 2u;j<limit;j+=2u)
3448 		{
3449 			model->indiceArr << offset+j-2u << offset+j-1u << offset+j;
3450 			model->indiceArr << offset+j << offset+j-1u << offset+j+1u;
3451 		}
3452 		t -= dt;
3453 	}
3454 }
3455 
3456 struct Ring3DModel
3457 {
3458 	QVector<float> vertexArr;
3459 	QVector<float> texCoordArr;
3460 	QVector<unsigned short> indiceArr;
3461 };
3462 
3463 
sRing(Ring3DModel * model,const float rMin,const float rMax,unsigned short int slices,const unsigned short int stacks)3464 void sRing(Ring3DModel* model, const float rMin, const float rMax, unsigned short int slices, const unsigned short int stacks)
3465 {
3466 	float x,y;
3467 
3468 	const float dr = (rMax-rMin) / stacks;
3469 	const float* cos_sin_theta = StelUtils::ComputeCosSinTheta(slices);
3470 	const float* cos_sin_theta_p;
3471 
3472 	model->vertexArr.resize(0);
3473 	model->texCoordArr.resize(0);
3474 	model->indiceArr.resize(0);
3475 
3476 	float r = rMin;
3477 	for (unsigned short int i=0; i<=stacks; ++i)
3478 	{
3479 		const float tex_r0 = (r-rMin)/(rMax-rMin);
3480 		unsigned short int j;
3481 		for (j=0,cos_sin_theta_p=cos_sin_theta; j<=slices; ++j,cos_sin_theta_p+=2)
3482 		{
3483 			x = r*cos_sin_theta_p[0];
3484 			y = r*cos_sin_theta_p[1];
3485 			model->texCoordArr << tex_r0 << 0.5f;
3486 			model->vertexArr << x << y << 0.f;
3487 		}
3488 		r+=dr;
3489 	}
3490 	for (unsigned short int i=0; i<stacks; ++i)
3491 	{
3492 		for (unsigned short int j=0; j<slices; ++j)
3493 		{
3494 			model->indiceArr << i*slices+j << (i+1)*slices+j << i*slices+j+1u;
3495 			model->indiceArr << i*slices+j+1u << (i+1u)*slices+j << (i+1u)*slices+j+1u;
3496 		}
3497 	}
3498 }
3499 
3500 // Used in drawSphere() to compute shadows.
computeModelMatrix(Mat4d & result,bool solarEclipseCase) const3501 void Planet::computeModelMatrix(Mat4d &result, bool solarEclipseCase) const
3502 {
3503 	result = Mat4d::translation(eclipticPos) * rotLocalToParent;
3504 	PlanetP p = parent;
3505 	switch (re.method)
3506 	{
3507 		case RotationElements::Traditional:
3508 			while (p && p->parent)
3509 			{
3510 				result = Mat4d::translation(p->eclipticPos) * result * p->rotLocalToParent;
3511 				p = p->parent;
3512 			}
3513 			break;
3514 		case RotationElements::WGCCRE:
3515 			while (p && p->parent)
3516 			{
3517 				result = Mat4d::translation(p->eclipticPos) * result;
3518 				p = p->parent;
3519 			}
3520 			break;
3521 	}
3522 	// WEIRD! The following has to be disabled to have correct solar eclipse sizes in InfoString.
3523 	// However, it has to be active for Lunar eclipse shadow rendering.
3524 	// Maybe SolarSystem::getSolarEclipseFactor() can be implemented without Planet::computeModelMatrix()
3525 	if ((englishName=="Moon") && !solarEclipseCase)
3526 	{
3527 		PlanetP sun=GETSTELMODULE(SolarSystem)->getSun();
3528 		// in our program we have no aberration push for the moon. We must take that info from the Sun's push instead
3529 		// It seems that a distance proportional to the distance lunarDistance/solarDistance may be the right way (?)
3530 		const double earthSunDistance=parent->eclipticPos.length();
3531 		const double earthMoonDistance=eclipticPos.length();
3532 		const double factor=earthMoonDistance/earthSunDistance;
3533 		result = Mat4d::translation(factor*sun->getAberrationPush()) * result * Mat4d::zrotation(M_PI/180.*static_cast<double>(axisRotation + 90.f));
3534 	}
3535 	else {
3536 		result = Mat4d::translation(aberrationPush) * result * Mat4d::zrotation(M_PI/180.*static_cast<double>(axisRotation + 90.f));
3537 	}
3538 }
3539 
setCommonShaderUniforms(const StelPainter & painter,QOpenGLShaderProgram * shader,const PlanetShaderVars & shaderVars)3540 Planet::RenderData Planet::setCommonShaderUniforms(const StelPainter& painter, QOpenGLShaderProgram* shader, const PlanetShaderVars& shaderVars) //const
3541 {
3542 	RenderData data;
3543 
3544 	const PlanetP sun = GETSTELMODULE(SolarSystem)->getSun();
3545 	const StelProjectorP& projector = painter.getProjector();
3546 
3547 	const Mat4f& m = projector->getProjectionMatrix();
3548 	const QMatrix4x4 qMat = m.convertToQMatrix();
3549 
3550 	computeModelMatrix(data.modelMatrix, false);
3551 	// used to project from solar system into local space
3552 	data.mTarget = data.modelMatrix.inverse();
3553 
3554 	data.shadowCandidates = getCandidatesForShadow();
3555 	// Our shader doesn't support more than 4 planets creating shadow
3556 	if (data.shadowCandidates.size()>4)
3557 	{
3558 		qDebug() << "Too many satellite shadows, some won't be displayed";
3559 		data.shadowCandidates.resize(4);
3560 	}
3561 	Mat4d shadowModelMatrix;
3562 	for (int i=0;i<data.shadowCandidates.size();++i)
3563 	{
3564 		data.shadowCandidates.at(i)->computeModelMatrix(shadowModelMatrix, false);
3565 		const Vec4d position = data.mTarget * shadowModelMatrix.getColumn(3);
3566 		data.shadowCandidatesData(0, i) = static_cast<float>(position[0]);
3567 		data.shadowCandidatesData(1, i) = static_cast<float>(position[1]);
3568 		data.shadowCandidatesData(2, i) = static_cast<float>(position[2]);
3569 		data.shadowCandidatesData(3, i) = static_cast<float>(data.shadowCandidates.at(i)->getEquatorialRadius());
3570 	}
3571 
3572 	Vec3f lightPos3=light.position.toVec3f();
3573 	projector->getModelViewTransform()->backward(lightPos3);
3574 	lightPos3.normalize();
3575 
3576 	data.eyePos = StelApp::getInstance().getCore()->getObserverHeliocentricEclipticPos();
3577 	//qDebug() << eyePos[0] << " " << eyePos[1] << " " << eyePos[2] << " --> ";
3578 	// Use refractionOff for avoiding flickering Moon. (Bug #1411958)
3579 	StelApp::getInstance().getCore()->getHeliocentricEclipticModelViewTransform(StelCore::RefractionOff)->forward(data.eyePos);
3580 	//qDebug() << "-->" << eyePos[0] << " " << eyePos[1] << " " << eyePos[2];
3581 	projector->getModelViewTransform()->backward(data.eyePos);
3582 	data.eyePos.normalize();
3583 	//qDebug() << " -->" << eyePos[0] << " " << eyePos[1] << " " << eyePos[2];
3584 	LandscapeMgr* lmgr=GETSTELMODULE(LandscapeMgr);
3585 	Q_ASSERT(lmgr);
3586 
3587 	GL(shader->setUniformValue(shaderVars.projectionMatrix, qMat));
3588 	GL(shader->setUniformValue(shaderVars.lightDirection, lightPos3[0], lightPos3[1], lightPos3[2]));
3589 	GL(shader->setUniformValue(shaderVars.eyeDirection, static_cast<GLfloat>(data.eyePos[0]), static_cast<GLfloat>(data.eyePos[1]), static_cast<GLfloat>(data.eyePos[2])));
3590 	GL(shader->setUniformValue(shaderVars.diffuseLight, light.diffuse[0], light.diffuse[1], light.diffuse[2]));
3591 	GL(shader->setUniformValue(shaderVars.ambientLight, light.ambient[0], light.ambient[1], light.ambient[2]));
3592 	GL(shader->setUniformValue(shaderVars.tex, 0));
3593 	GL(shader->setUniformValue(shaderVars.shadowCount, data.shadowCandidates.size()));
3594 	GL(shader->setUniformValue(shaderVars.shadowData, data.shadowCandidatesData));
3595 	GL(shader->setUniformValue(shaderVars.sunInfo, static_cast<GLfloat>(data.mTarget[12]), static_cast<GLfloat>(data.mTarget[13]), static_cast<GLfloat>(data.mTarget[14]), static_cast<GLfloat>(sun->getEquatorialRadius())));
3596 	GL(shader->setUniformValue(shaderVars.skyBrightness, lmgr->getAtmosphereAverageLuminance()));
3597 
3598 	if(shaderVars.orenNayarParameters>=0)
3599 	{
3600 		//calculate and set oren-nayar parameters. The 75 in the scaling computation is arbitrary, to make the terminator visible.
3601 		const float roughnessSq = roughness * roughness;
3602 		QVector4D vec(
3603 					1.0f - 0.5f * roughnessSq / (roughnessSq + 0.33f), // 0.57f), //x = A. If interreflection term is removed from shader, use 0.57 instead of 0.33.
3604 					0.45f * roughnessSq / (roughnessSq + 0.09f),	//y = B
3605 					50.0f * albedo/M_PIf, // was: 1.85f, but unclear why. //z = scale factor=rho/pi*Eo. rho=albedo=0.12, Eo~50? Higher Eo looks better!
3606 					roughnessSq);
3607 		GL(shader->setUniformValue(shaderVars.orenNayarParameters, vec));
3608 	}
3609 
3610 	float outgas_intensity_distanceScaled=static_cast<float>(static_cast<double>(outgas_intensity)/getHeliocentricEclipticPos().lengthSquared()); // ad-hoc function: assume square falloff by distance.
3611 	GL(shader->setUniformValue(shaderVars.outgasParameters, QVector2D(outgas_intensity_distanceScaled, outgas_falloff)));
3612 
3613 	return data;
3614 }
3615 
drawSphere(StelPainter * painter,float screenSz,bool drawOnlyRing)3616 void Planet::drawSphere(StelPainter* painter, float screenSz, bool drawOnlyRing)
3617 {
3618 	const float sphereScaleF=static_cast<float>(sphereScale);
3619 	if (texMap)
3620 	{
3621 		// For lazy loading, return if texture not yet loaded
3622 		if (!texMap->bind(0))
3623 		{
3624 			return;
3625 		}
3626 	}
3627 
3628 	painter->setBlending(false);
3629 	painter->setCullFace(true);
3630 
3631 	// Draw the spheroid itself
3632 	// Adapt the number of facets according with the size of the sphere for optimization
3633 	const unsigned short int nb_facet = static_cast<unsigned short int>(qBound(10u, static_cast<uint>(screenSz * 40.f/50.f * sqrt(sphereScaleF)), 100u));	// 40 facets for 1024 pixels diameter on screen
3634 
3635 	// Generates the vertices
3636 	Planet3DModel model;
3637 	sSphere(&model, static_cast<float>(equatorialRadius), static_cast<float>(oneMinusOblateness), nb_facet, nb_facet);
3638 
3639 	QVector<float> projectedVertexArr(model.vertexArr.size());
3640 	for (int i=0;i<model.vertexArr.size()/3;++i)
3641 	{
3642 		Vec3f p = *(reinterpret_cast<const Vec3f*>(model.vertexArr.constData()+i*3));
3643 		p *= sphereScaleF;
3644 		painter->getProjector()->project(p, *(reinterpret_cast<Vec3f*>(projectedVertexArr.data()+i*3)));
3645 	}
3646 
3647 	const SolarSystem* ssm = GETSTELMODULE(SolarSystem);
3648 
3649 	if (this==ssm->getSun())
3650 	{
3651 		texMap->bind();
3652 		//painter->setColor(2, 2, 0.2); // This is now in draw3dModel() to apply extinction
3653 		painter->setArrays(reinterpret_cast<const Vec3f*>(projectedVertexArr.constData()), reinterpret_cast<const Vec2f*>(model.texCoordArr.constData()));
3654 		painter->drawFromArray(StelPainter::Triangles, model.indiceArr.size(), 0, false, model.indiceArr.constData());
3655 		return;
3656 	}
3657 
3658 	//cancel out if shaders are invalid
3659 	if(shaderError)
3660 		return;
3661 
3662 	QOpenGLShaderProgram* shader = planetShaderProgram;
3663 	const PlanetShaderVars* shaderVars = &planetShaderVars;
3664 	if (rings)
3665 	{
3666 		shader = ringPlanetShaderProgram;
3667 		shaderVars = &ringPlanetShaderVars;
3668 	}
3669 	if (this==ssm->getMoon())
3670 	{
3671 		shader = moonShaderProgram;
3672 		shaderVars = &moonShaderVars;
3673 	}
3674 	//check if shaders are loaded
3675 	if(!shader)
3676 	{
3677 		Planet::initShader();
3678 
3679 		if(shaderError)
3680 		{
3681 			qCritical()<<"Can't use planet drawing, shaders invalid!";
3682 			return;
3683 		}
3684 
3685 		shader = planetShaderProgram;
3686 		if (rings)
3687 		{
3688 			shader = ringPlanetShaderProgram;
3689 		}
3690 		if (this==ssm->getMoon())
3691 		{
3692 			shader = moonShaderProgram;
3693 		}
3694 	}
3695 
3696 	GL(shader->bind());
3697 
3698 	RenderData rData = setCommonShaderUniforms(*painter,shader,*shaderVars);
3699 
3700 	if (rings!=Q_NULLPTR)
3701 	{
3702 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.isRing, false));
3703 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.ring, true));
3704 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.outerRadius, rings->radiusMax));
3705 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.innerRadius, rings->radiusMin));
3706 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.ringS, 2));
3707 		rings->tex->bind(2);
3708 	}
3709 
3710 	if (this==ssm->getMoon())
3711 	{
3712 		GL(normalMap->bind(2));
3713 		GL(moonShaderProgram->setUniformValue(moonShaderVars.normalMap, 2));
3714 		if (!rData.shadowCandidates.isEmpty())
3715 		{
3716 			GL(texEarthShadow->bind(3));
3717 			GL(moonShaderProgram->setUniformValue(moonShaderVars.earthShadow, 3));
3718 			// Ad-hoc visibility improvement during lunar eclipses:
3719 			// During partial umbra phase, make moon brighter so that the bright limb and umbra border has more visibility.
3720 			// When the moon is half in umbra, we start to raise its brightness. Near edge of totality we try to simulate the apparent super-bright edge.
3721 			static const double tweak=1.015; // 1.00 to have maximum push only in full umbra. 1.01 or even 1.02 looks better to show a brilliant last/first edge
3722 			GLfloat push=1.0f;
3723 
3724 			// Like in getVMagnitude() we must compute an elongation from the aberrated sun.
3725 			PlanetP sun=ssm->getSun();
3726 			const Vec3d obsPos=parent->eclipticPos-sun->getAberrationPush();
3727 			const double observerRq = obsPos.lengthSquared();
3728 			const Vec3d& planetHelioPos = getHeliocentricEclipticPos() - sun->getAberrationPush();
3729 			const double planetRq = planetHelioPos.lengthSquared();
3730 			const double observerPlanetRq = (obsPos - planetHelioPos).lengthSquared();
3731 			double aberratedElongation = std::acos((observerPlanetRq  + observerRq - planetRq)/(2.0*std::sqrt(observerPlanetRq*observerRq)));
3732 			const double od = 180. - aberratedElongation * (180.0/M_PI); // opposition distance [degrees]
3733 
3734 			// Compute umbra radius at lunar distance.
3735 			const double Lambda=getEclipticPos().length();                             // Lunar distance [AU]
3736 			const double sigma=ssm->getEarthShadowRadiiAtLunarDistance().first[0]/3600.;
3737 			const double tau=atan(getEquatorialRadius()/Lambda) * M_180_PI; // geocentric angle of Lunar radius [degrees]
3738 
3739 			if (od<tweak*sigma-tau)     // if the Moon is fully immersed in the shadow
3740 				push=4.0f;
3741 			else if (od<tweak*sigma)    // If the Moon is half immersed, start pushing with a strong power function that make it apparent only in the last few percents.
3742 				push+=3.f*(1.f-pow(static_cast<float>((od-tweak*sigma+tau)/tau), 1.f/6.f));
3743 
3744 			GL(moonShaderProgram->setUniformValue(moonShaderVars.eclipsePush, push)); // constant for now...
3745 		}
3746 	}
3747 
3748 	GL(shader->setAttributeArray(shaderVars->vertex, static_cast<const GLfloat*>(projectedVertexArr.constData()), 3));
3749 	GL(shader->enableAttributeArray(shaderVars->vertex));
3750 	GL(shader->setAttributeArray(shaderVars->unprojectedVertex, static_cast<const GLfloat*>(model.vertexArr.constData()), 3));
3751 	GL(shader->enableAttributeArray(shaderVars->unprojectedVertex));
3752 	GL(shader->setAttributeArray(shaderVars->texCoord, static_cast<const GLfloat*>(model.texCoordArr.constData()), 2));
3753 	GL(shader->enableAttributeArray(shaderVars->texCoord));
3754 
3755 	if (rings && !drawOnlyRing)
3756 	{
3757 		painter->setDepthMask(true);
3758 		painter->setDepthTest(true);
3759 		gl->glClear(GL_DEPTH_BUFFER_BIT);
3760 	}
3761 
3762 	if (!drawOnlyRing)
3763 		GL(gl->glDrawElements(GL_TRIANGLES, model.indiceArr.size(), GL_UNSIGNED_SHORT, model.indiceArr.constData()));
3764 
3765 	if (rings)
3766 	{
3767 		// Draw the rings just after the planet
3768 		painter->setDepthMask(false);
3769 
3770 		// Normal transparency mode
3771 		painter->setBlending(true);
3772 
3773 		Ring3DModel ringModel;
3774 		sRing(&ringModel, rings->radiusMin, rings->radiusMax, 128, 32);
3775 
3776 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.isRing, true));
3777 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.tex, 2));
3778 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.ringS, 1));
3779 
3780 		QMatrix4x4 shadowCandidatesData;
3781 		const Vec4d position = rData.mTarget * rData.modelMatrix.getColumn(3);
3782 		shadowCandidatesData(0, 0) = static_cast<float>(position[0]);
3783 		shadowCandidatesData(1, 0) = static_cast<float>(position[1]);
3784 		shadowCandidatesData(2, 0) = static_cast<float>(position[2]);
3785 		shadowCandidatesData(3, 0) = static_cast<float>(getEquatorialRadius());
3786 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.shadowCount, 1));
3787 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.shadowData, shadowCandidatesData));
3788 
3789 		projectedVertexArr.resize(ringModel.vertexArr.size());
3790 		for (int i=0;i<ringModel.vertexArr.size()/3;++i)
3791 		{
3792 			Vec3f p = *(reinterpret_cast<const Vec3f*>(ringModel.vertexArr.constData()+i*3));
3793 			p *= sphereScaleF;
3794 			painter->getProjector()->project(p, *(reinterpret_cast<Vec3f*>(projectedVertexArr.data()+i*3)));
3795 		}
3796 
3797 		GL(ringPlanetShaderProgram->setAttributeArray(ringPlanetShaderVars.vertex, reinterpret_cast<const GLfloat*>(projectedVertexArr.constData()), 3));
3798 		GL(ringPlanetShaderProgram->enableAttributeArray(ringPlanetShaderVars.vertex));
3799 		GL(ringPlanetShaderProgram->setAttributeArray(ringPlanetShaderVars.unprojectedVertex, reinterpret_cast<const GLfloat*>(ringModel.vertexArr.constData()), 3));
3800 		GL(ringPlanetShaderProgram->enableAttributeArray(ringPlanetShaderVars.unprojectedVertex));
3801 		GL(ringPlanetShaderProgram->setAttributeArray(ringPlanetShaderVars.texCoord, reinterpret_cast<const GLfloat*>(ringModel.texCoordArr.constData()), 2));
3802 		GL(ringPlanetShaderProgram->enableAttributeArray(ringPlanetShaderVars.texCoord));
3803 
3804 		if (rData.eyePos[2]<0)
3805 			gl->glCullFace(GL_FRONT);
3806 
3807 		GL(gl->glDrawElements(GL_TRIANGLES, ringModel.indiceArr.size(), GL_UNSIGNED_SHORT, ringModel.indiceArr.constData()));
3808 
3809 		if (rData.eyePos[2]<0)
3810 			gl->glCullFace(GL_BACK);
3811 
3812 		painter->setDepthTest(false);
3813 	}
3814 
3815 	GL(shader->release());
3816 
3817 	painter->setCullFace(false);
3818 }
3819 
3820 // Draw the Hips survey.
drawSurvey(StelCore * core,StelPainter * painter)3821 void Planet::drawSurvey(StelCore* core, StelPainter* painter)
3822 {
3823 	if (!Planet::initShader()) return;
3824 	const SolarSystem* ssm = GETSTELMODULE(SolarSystem);
3825 
3826 	painter->setDepthMask(true);
3827 	painter->setDepthTest(true);
3828 
3829 	// Backup transformation so that we can restore it later.
3830 	StelProjector::ModelViewTranformP transfo = painter->getProjector()->getModelViewTransform()->clone();
3831 	Vec4f color = painter->getColor();
3832 	painter->getProjector()->getModelViewTransform()->combine(Mat4d::scaling(equatorialRadius * sphereScale));
3833 
3834 	QOpenGLShaderProgram* shader = planetShaderProgram;
3835 	const PlanetShaderVars* shaderVars = &planetShaderVars;
3836 	if (rings)
3837 	{
3838 		shader = ringPlanetShaderProgram;
3839 		shaderVars = &ringPlanetShaderVars;
3840 	}
3841 	if (this == ssm->getMoon())
3842 	{
3843 		shader = moonShaderProgram;
3844 		shaderVars = &moonShaderVars;
3845 	}
3846 
3847 	GL(shader->bind());
3848 	RenderData rData = setCommonShaderUniforms(*painter, shader, *shaderVars);
3849 	QVector<Vec3f> projectedVertsArray;
3850 	QVector<Vec3f> vertsArray;
3851 	double angle = getSpheroidAngularSize(core) * M_PI_180;
3852 
3853 	if (rings)
3854 	{
3855 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.isRing, false));
3856 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.ring, true));
3857 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.outerRadius, rings->radiusMax));
3858 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.innerRadius, rings->radiusMin));
3859 		GL(ringPlanetShaderProgram->setUniformValue(ringPlanetShaderVars.ringS, 2));
3860 		rings->tex->bind(2);
3861 	}
3862 
3863 	if (this == ssm->getMoon())
3864 	{
3865 		GL(normalMap->bind(2));
3866 		GL(moonShaderProgram->setUniformValue(moonShaderVars.normalMap, 2));
3867 		if (!rData.shadowCandidates.isEmpty())
3868 		{
3869 			GL(texEarthShadow->bind(3));
3870 			GL(moonShaderProgram->setUniformValue(moonShaderVars.earthShadow, 3));
3871 		}
3872 	}
3873 
3874 	// Apply a rotation otherwize the hips surveys don't get rendered at the
3875 	// proper position.  Not sure why...
3876 	painter->getProjector()->getModelViewTransform()->combine(Mat4d::zrotation(M_PI * 0.5));
3877 	painter->getProjector()->getModelViewTransform()->combine(Mat4d::scaling(Vec3d(1, 1, oneMinusOblateness)));
3878 
3879 	survey->draw(painter, angle, [&](const QVector<Vec3d>& verts, const QVector<Vec2f>& tex, const QVector<uint16_t>& indices) {
3880 		projectedVertsArray.resize(verts.size());
3881 		vertsArray.resize(verts.size());
3882 		for (int i = 0; i < verts.size(); i++)
3883 		{
3884 			Vec3d v = verts[i];
3885 			painter->getProjector()->project(v, v);
3886 			projectedVertsArray[i] = v.toVec3f();
3887 			v = Mat4d::scaling(equatorialRadius) * verts[i];
3888 			v = Mat4d::scaling(Vec3d(1, 1, oneMinusOblateness)) * v;
3889 			// Undo the rotation we applied for the survey fix.
3890 			v = Mat4d::zrotation(M_PI * 0.5) * v;
3891 			vertsArray[i] = v.toVec3f();
3892 		}
3893 		GL(shader->setAttributeArray(shaderVars->vertex, reinterpret_cast<const GLfloat*>(projectedVertsArray.constData()), 3));
3894 		GL(shader->enableAttributeArray(shaderVars->vertex));
3895 		GL(shader->setAttributeArray(shaderVars->unprojectedVertex, reinterpret_cast<const GLfloat*>(vertsArray.constData()), 3));
3896 		GL(shader->enableAttributeArray(shaderVars->unprojectedVertex));
3897 		GL(shader->setAttributeArray(shaderVars->texCoord, reinterpret_cast<const GLfloat*>(tex.constData()), 2));
3898 		GL(shader->enableAttributeArray(shaderVars->texCoord));
3899 		GL(gl->glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.constData()));
3900 	});
3901 
3902 	// Restore painter state.
3903 	painter->setProjector(core->getProjection(transfo));
3904 	painter->setColor(color);
3905 }
3906 
loadObjModel() const3907 Planet::PlanetOBJModel* Planet::loadObjModel() const
3908 {
3909 	PlanetOBJModel* mdl = new PlanetOBJModel();
3910 	if(!mdl->obj->load(objModelPath))
3911 	{
3912 		//object loading failed
3913 		qCritical()<<"Could not load planet OBJ model for"<<englishName;
3914 		delete mdl;
3915 		return Q_NULLPTR;
3916 	}
3917 
3918 	//ideally, all planet OBJs should only have a single object with a single material
3919 	if(mdl->obj->getObjectList().size()>1)
3920 		qWarning()<<"Planet OBJ model has more than one object defined, this may cause problems ...";
3921 	if(mdl->obj->getMaterialList().size()>1)
3922 		qWarning()<<"Planet OBJ model has more than one material defined, this may cause problems ...";
3923 
3924 	//start texture loading
3925 	const StelOBJ::Material& mat = mdl->obj->getMaterialList().at(mdl->obj->getObjectList().first().groups.first().materialIndex);
3926 	if(mat.map_Kd.isEmpty())
3927 	{
3928 		//we use a custom 1x1 pixel texture in this case
3929 		qWarning()<<"Planet OBJ model for"<<englishName<<"has no diffuse texture";
3930 	}
3931 	else
3932 	{
3933 		//this call starts loading the tex in background
3934 		mdl->texture = StelApp::getInstance().getTextureManager().createTextureThread(mat.map_Kd,StelTexture::StelTextureParams(true,GL_LINEAR,GL_REPEAT,true),false);
3935 	}
3936 
3937 	//extract the pos array into separate vector, it is the only one we need on CPU side for drawing
3938 	mdl->obj->splitVertexData(&mdl->posArray);
3939 	mdl->bbox = mdl->obj->getAABBox();
3940 
3941 	return mdl;
3942 }
3943 
ensureObjLoaded()3944 bool Planet::ensureObjLoaded()
3945 {
3946 	if(!objModel && !objModelLoader)
3947 	{
3948 		qDebug()<<"Queueing aysnc load of OBJ model for"<<englishName;
3949 		//create the async OBJ model loader
3950 		objModelLoader = new QFuture<PlanetOBJModel*>(QtConcurrent::run(this,&Planet::loadObjModel));
3951 	}
3952 
3953 	if(objModelLoader)
3954 	{
3955 		if(objModelLoader->isFinished())
3956 		{
3957 			//the model loading has just finished, save the result
3958 			objModel = objModelLoader->result();
3959 			delete objModelLoader; //we dont need the result anymore
3960 			objModelLoader = Q_NULLPTR;
3961 
3962 			if(!objModel)
3963 			{
3964 				//model load failed, fall back to sphere mode
3965 				objModelPath.clear();
3966 				qWarning()<<"Cannot load OBJ model for solar system object"<<getEnglishName();
3967 				return false;
3968 			}
3969 			else
3970 			{
3971 				//load model data into GL
3972 				if(!objModel->loadGL())
3973 				{
3974 					delete objModel;
3975 					objModel = Q_NULLPTR;
3976 					objModelPath.clear();
3977 					qWarning()<<"Cannot load OBJ model into OpenGL for solar system object"<<getEnglishName();
3978 					return false;
3979 				}
3980 				GL(;);
3981 			}
3982 		}
3983 		else
3984 		{
3985 			//we are still loading, use the sphere method for drawing
3986 			return false;
3987 		}
3988 	}
3989 
3990 	//OBJ model is valid!
3991 	return true;
3992 }
3993 
drawObjModel(StelPainter * painter,float screenSz)3994 bool Planet::drawObjModel(StelPainter *painter, float screenSz)
3995 {
3996 	Q_UNUSED(screenSz); //screen size unused for now, use it for LOD or something?
3997 
3998 	//make sure the OBJ is loaded, or start loading it
3999 	if(!ensureObjLoaded())
4000 		return false;
4001 
4002 	if(shaderError)
4003 	{
4004 		qDebug() << "Planet::drawObjModel: Something went wrong with shader initialisation. Cannot draw OBJs, using spheres instead.";
4005 		return false;
4006 	}
4007 
4008 	const SolarSystem* ssm = GETSTELMODULE(SolarSystem);
4009 
4010 	QMatrix4x4 shadowMatrix;
4011 	bool shadowmapping = false;
4012 	if(ssm->getFlagShowObjSelfShadows())
4013 		shadowmapping = drawObjShadowMap(painter,shadowMatrix);
4014 
4015 	if(objModel->texture)
4016 	{
4017 		if(!objModel->texture->bind())
4018 		{
4019 			//the texture is still loading, use the sphere method
4020 			return false;
4021 		}
4022 	}
4023 	else
4024 	{
4025 		//HACK: there is no texture defined, we create a 1x1 pixel texture with color*albedo
4026 		//this is not the most efficient method, but prevents having to rewrite the shader to work without a texture
4027 		//removing some complexity in managing this use-case
4028 		Vec3f texCol = haloColor * albedo * 255.0f + Vec3f(0.5f);
4029 		//convert to byte
4030 		Vector3<GLubyte> colByte(static_cast<unsigned char>(texCol[0]),static_cast<unsigned char>(texCol[1]),static_cast<unsigned char>(texCol[2]));
4031 		GLuint tex;
4032 		gl->glActiveTexture(GL_TEXTURE0);
4033 		gl->glGenTextures(1, &tex);
4034 		gl->glBindTexture(GL_TEXTURE_2D,tex);
4035 		GLint oldalignment;
4036 		gl->glGetIntegerv(GL_UNPACK_ALIGNMENT,&oldalignment);
4037 		gl->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
4038 		gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, colByte.v );
4039 		gl->glPixelStorei(GL_UNPACK_ALIGNMENT, oldalignment);
4040 
4041 		gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
4042 		gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
4043 		gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
4044 		gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
4045 
4046 		// create a StelTexture from this
4047 		objModel->texture = StelApp::getInstance().getTextureManager().wrapperForGLTexture(tex);
4048 	}
4049 
4050 	if(objModel->needsRescale)
4051 		objModel->performScaling(AU_KM * sphereScale);
4052 
4053 	//the model is ready to draw!
4054 	painter->setBlending(false);
4055 	painter->setCullFace(true);
4056 	gl->glCullFace(GL_BACK);
4057 	//depth testing is required here
4058 	painter->setDepthTest(true);
4059 	painter->setDepthMask(true);
4060 	gl->glClear(GL_DEPTH_BUFFER_BIT);
4061 
4062 	// Bind the array
4063 	GL(objModel->arr->bind());
4064 
4065 	//set up shader
4066 	QOpenGLShaderProgram* shd = objShaderProgram;
4067 	PlanetShaderVars* shdVars = &objShaderVars;
4068 	if(shadowmapping)
4069 	{
4070 		shd = objShadowShaderProgram;
4071 		shdVars = &objShadowShaderVars;
4072 		gl->glActiveTexture(GL_TEXTURE1);
4073 		gl->glBindTexture(GL_TEXTURE_2D,shadowTex);
4074 		GL(shd->bind());
4075 		GL(shd->setUniformValue(shdVars->shadowMatrix, shadowMatrix));
4076 		GL(shd->setUniformValue(shdVars->shadowTex, 1));
4077 	}
4078 	else
4079 		shd->bind();
4080 
4081 	//project the data
4082 	//because the StelOpenGLArray might use a VAO, we have to use a OGL buffer here in all cases
4083 	objModel->projPosBuffer->bind();
4084 	const int vtxCount = objModel->posArray.size();
4085 
4086 	const StelProjectorP& projector = painter->getProjector();
4087 
4088 	// I tested buffer orphaning (https://www.opengl.org/wiki/Buffer_Object_Streaming#Buffer_re-specification),
4089 	// but it seems there is not really much of a effect here (probably because we are already pretty CPU-bound).
4090 	// Also, map()-ing the buffer directly, like:
4091 	//	Vec3f* bufPtr = static_cast<Vec3f*>(objModel->projPosBuffer->map(QOpenGLBuffer::WriteOnly));
4092 	//	projector->project(vtxCount,objModel->posArray.constData(),bufPtr);
4093 	//	objModel->projPosBuffer->unmap();
4094 	// caused a 40% FPS drop for some reason!
4095 	// (in theory, this should be faster because it should avoid copying the array)
4096 	// So, lets not do that and just use this simple way in all cases:
4097 	projector->project(vtxCount, objModel->scaledArray.constData(),objModel->projectedPosArray.data());
4098 	objModel->projPosBuffer->allocate(objModel->projectedPosArray.constData(),vtxCount * static_cast<int>(sizeof(Vec3f)));
4099 
4100 	//unprojectedVertex, normalIn and texCoord are set by the StelOpenGLArray
4101 	//we only need to set the freshly projected data
4102 	GL(shd->setAttributeArray("vertex",GL_FLOAT,Q_NULLPTR,3,0));
4103 	GL(shd->enableAttributeArray("vertex"));
4104 	objModel->projPosBuffer->release();
4105 
4106 	setCommonShaderUniforms(*painter,shd,*shdVars);
4107 
4108 	//draw that model using the array wrapper
4109 	objModel->arr->draw();
4110 
4111 	shd->disableAttributeArray("vertex");
4112 	shd->release();
4113 	objModel->arr->release();
4114 
4115 	painter->setCullFace(false);
4116 	painter->setDepthTest(false);
4117 
4118 	return true;
4119 }
4120 
drawObjShadowMap(StelPainter * painter,QMatrix4x4 & shadowMatrix)4121 bool Planet::drawObjShadowMap(StelPainter *painter, QMatrix4x4& shadowMatrix)
4122 {
4123 	if(!shadowInitialized)
4124 		if(!initFBO())
4125 		{
4126 			qDebug()<<"Cannot draw OBJ self-shadow";
4127 			return false;
4128 		}
4129 
4130 	const StelProjectorP projector = painter->getProjector();
4131 
4132 	//find the light direction in model space
4133 	//Mat4d modelMatrix;
4134 	//computeModelMatrix(modelMatrix);
4135 	//Mat4d worldToModel = modelMatrix.inverse();
4136 
4137 	Vec3d lightDir = light.position;
4138 	projector->getModelViewTransform()->backward(lightDir);
4139 	//Vec3d lightDir(worldToModel[12], worldToModel[13], worldToModel[14]);
4140 	lightDir.normalize();
4141 
4142 	//use a distance of 1km to the origin for additional precision, instead of 1AU
4143 	Vec3d lightPosScaled = lightDir;
4144 
4145 	//the camera looks to the origin
4146 	QMatrix4x4 modelView;
4147 	modelView.lookAt(QVector3D(static_cast<float>(lightPosScaled[0]),static_cast<float>(lightPosScaled[1]),static_cast<float>(lightPosScaled[2])), QVector3D(), QVector3D(0.f,0.f,1.f));
4148 
4149 	//create an orthographic projection encompassing the AABB
4150 	double maxZ = -std::numeric_limits<double>::max();
4151 	double minZ = std::numeric_limits<double>::max();
4152 	double maxUp = -std::numeric_limits<double>::max();
4153 	double minUp = std::numeric_limits<double>::max();
4154 	double maxRight = -std::numeric_limits<double>::max();
4155 	double minRight = std::numeric_limits<double>::max();
4156 
4157 	//create an orthonormal system using 2-step Gram-Schmidt + cross product
4158 	// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
4159 	Vec3d vDir = -lightDir; //u1/e1, view direction
4160 	Vec3d up = Vec3d(0.0, 0.0, 1.0); //v2
4161 	up = up - up.dot(vDir) * vDir; //u2 = v2 - proj_u1(v2)
4162 	up.normalize(); //e2
4163 
4164 	Vec3d right = vDir^up;
4165 	right.normalize();
4166 
4167 	for(unsigned int i=0; i<AABBox::CORNERCOUNT; i++)
4168 	{
4169 		Vec3d v = objModel->bbox.getCorner(static_cast<AABBox::Corner>(i)).toVec3d();
4170 		Vec3d fromCam = v - lightPosScaled; //vector from cam to vertex
4171 
4172 		//project the fromCam vector onto the 3 vectors of the orthonormal system
4173 		double dist = fromCam.dot(vDir);
4174 		maxZ = std::max(dist, maxZ);
4175 		minZ = std::min(dist, minZ);
4176 
4177 		dist = fromCam.dot(right);
4178 		minRight = std::min(dist, minRight);
4179 		maxRight = std::max(dist, maxRight);
4180 
4181 		dist = fromCam.dot(up);
4182 		minUp = std::min(dist, minUp);
4183 		maxUp = std::max(dist, maxUp);
4184 	}
4185 
4186 	QMatrix4x4 proj;
4187 	proj.ortho(static_cast<float>(minRight),static_cast<float>(maxRight),
4188 		   static_cast<float>(minUp),static_cast<float>(maxUp),
4189 		   static_cast<float>(minZ),static_cast<float>(maxZ));
4190 
4191 	QMatrix4x4 mvp = proj * modelView;
4192 
4193 	//bias matrix for lookup
4194 	static const QMatrix4x4 biasMatrix(0.5f, 0.0f, 0.0f, 0.5f,
4195 					   0.0f, 0.5f, 0.0f, 0.5f,
4196 					   0.0f, 0.0f, 0.5f, 0.5f,
4197 					   0.0f, 0.0f, 0.0f, 1.0f);
4198 	shadowMatrix = biasMatrix * mvp;
4199 
4200 	painter->setDepthTest(true);
4201 	painter->setDepthMask(true);
4202 	painter->setCullFace(true);
4203 	gl->glCullFace(GL_BACK);
4204 	bool useOffset = !qFuzzyIsNull(shadowPolyOffset.lengthSquared());
4205 
4206 	if(useOffset)
4207 	{
4208 		gl->glEnable(GL_POLYGON_OFFSET_FILL);
4209 		gl->glPolygonOffset(shadowPolyOffset[0], shadowPolyOffset[1]);
4210 	}
4211 
4212 	gl->glViewport(0,0,SM_SIZE,SM_SIZE);
4213 
4214 	GL(objModel->arr->bind());
4215 	GL(transformShaderProgram->bind());
4216 	GL(transformShaderProgram->setUniformValue(transformShaderVars.projectionMatrix, mvp));
4217 
4218 #ifdef DEBUG_SHADOWMAP
4219 	shadowFBO->bind();
4220 	objModel->texture->bind();
4221 	transformShaderProgram->setUniformValue(transformShaderVars.tex, 0);
4222 	gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
4223 #else
4224 	gl->glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO);
4225 	gl->glClear(GL_DEPTH_BUFFER_BIT);
4226 #endif
4227 
4228 	GL(objModel->arr->draw());
4229 
4230 	transformShaderProgram->release();
4231 	objModel->arr->release();
4232 
4233 #ifdef DEBUG_SHADOWMAP
4234 	//copy depth buffer into shadowTex
4235 	gl->glActiveTexture(GL_TEXTURE1);
4236 	gl->glBindTexture(GL_TEXTURE_2D,shadowTex);
4237 
4238 	//this is probably unsupported on an OGL ES2 context! just don't use DEBUG_SHADOWMAP here...
4239 	GL(QOpenGLContext::currentContext()->functions()->glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, SM_SIZE, SM_SIZE, 0));
4240 #endif
4241 	gl->glBindFramebuffer(GL_FRAMEBUFFER, StelApp::getInstance().getDefaultFBO());
4242 
4243 	//reset viewport (see StelPainter::setProjector)
4244 	const Vec4i& vp = projector->getViewport();
4245 	gl->glViewport(vp[0], vp[1], vp[2], vp[3]);
4246 
4247 	if(useOffset)
4248 	{
4249 		gl->glDisable(GL_POLYGON_OFFSET_FILL);
4250 		GL(gl->glPolygonOffset(0.0f,0.0f));
4251 	}
4252 
4253 #ifdef DEBUG_SHADOWMAP
4254 	//display the FB contents on-screen
4255 	QOpenGLFramebufferObject::blitFramebuffer(Q_NULLPTR,shadowFBO);
4256 #endif
4257 
4258 	return true;
4259 }
4260 
drawHints(const StelCore * core,const QFont & planetNameFont)4261 void Planet::drawHints(const StelCore* core, const QFont& planetNameFont)
4262 {
4263 	if (labelsFader.getInterstate()<=0.f)
4264 		return;
4265 
4266 	const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000);
4267 	StelPainter sPainter(prj);
4268 	sPainter.setFont(planetNameFont);
4269 	// Draw nameI18 + scaling if it's not == 1.
4270 	float tmp = (hintFader.getInterstate()<=0.f ? 7.f : 10.f) + static_cast<float>(getAngularSize(core)*M_PI/180.)*prj->getPixelPerRadAtCenter()/1.44f; // Shift for nameI18 printing
4271 	sPainter.setColor(labelColor,labelsFader.getInterstate());
4272 	sPainter.drawText(static_cast<float>(screenPos[0]),static_cast<float>(screenPos[1]), getPlanetLabel(), 0, tmp, tmp, false);
4273 
4274 	// hint disappears smoothly on close view
4275 	if (hintFader.getInterstate()<=0)
4276 		return;
4277 	tmp -= 10.f;
4278 	if (tmp<1) tmp=1;
4279 	sPainter.setColor(labelColor,labelsFader.getInterstate()*hintFader.getInterstate()/tmp*0.7f);
4280 
4281 	// Draw the 2D small circle
4282 	sPainter.setBlending(true);
4283 	Planet::hintCircleTex->bind();
4284 	sPainter.drawSprite2dMode(static_cast<float>(screenPos[0]), static_cast<float>(screenPos[1]), 11);
4285 }
4286 
Ring(float radiusMin,float radiusMax,const QString & texname)4287 Ring::Ring(float radiusMin, float radiusMax, const QString &texname)
4288 	:radiusMin(radiusMin),radiusMax(radiusMax)
4289 {
4290 	tex = StelApp::getInstance().getTextureManager().createTexture(StelFileMgr::getInstallationDir()+"/textures/"+texname);
4291 }
4292 
getCurrentOrbitColor() const4293 Vec3f Planet::getCurrentOrbitColor() const
4294 {
4295 	Vec3f orbColor = orbitColor;
4296 	switch(orbitColorStyle)
4297 	{
4298 		case ocsGroups:
4299 		{
4300 			const QMap<Planet::PlanetType, Vec3f> typeColorMap = {
4301 				{ isMoon,         orbitMoonsColor       },
4302 				{ isPlanet,       orbitMajorPlanetsColor},
4303 				{ isAsteroid,     orbitMinorPlanetsColor},
4304 				{ isDwarfPlanet,  orbitDwarfPlanetsColor},
4305 				{ isCubewano,     orbitCubewanosColor   },
4306 				{ isPlutino,      orbitPlutinosColor    },
4307 				{ isSDO,          orbitScatteredDiscObjectsColor},
4308 				{ isOCO,          orbitOortCloudObjectsColor},
4309 				{ isComet,        orbitCometsColor      },
4310 				{ isSednoid,      orbitSednoidsColor    },
4311 				{ isInterstellar, orbitInterstellarColor}};
4312 			orbColor = typeColorMap.value(pType, orbitColor);
4313 			break;
4314 		}
4315 		case ocsMajorPlanets:
4316 		{
4317 			const QString pName = getEnglishName().toLower();
4318 			const QMap<QString, Vec3f>majorPlanetColorMap = {
4319 				{ "mercury", orbitMercuryColor},
4320 				{ "venus",   orbitVenusColor  },
4321 				{ "earth",   orbitEarthColor  },
4322 				{ "mars",    orbitMarsColor   },
4323 				{ "jupiter", orbitJupiterColor},
4324 				{ "saturn",  orbitSaturnColor },
4325 				{ "uranus",  orbitUranusColor },
4326 				{ "neptune", orbitNeptuneColor}};
4327 			orbColor=majorPlanetColorMap.value(pName, orbitColor);
4328 			break;
4329 		}
4330 		case ocsOneColor:
4331 		{
4332 			orbColor = orbitColor;
4333 			break;
4334 		}
4335 	}
4336 
4337 	return orbColor;
4338 }
4339 
computeOrbit()4340 void Planet::computeOrbit()
4341 {
4342 	double dateJDE = lastJDE;
4343 	double calc_date;
4344 	Vec3d parentPos;
4345 	if (parent)
4346 		parentPos = parent->getHeliocentricEclipticPos(dateJDE)+ parent->getAberrationPush(); // aberrationPush is not strictly correct, but helps a lot...
4347 
4348 	for(int d = 0; d < ORBIT_SEGMENTS; d++)
4349 	{
4350 		calc_date = dateJDE + (d-ORBIT_SEGMENTS/2)*deltaOrbitJDE;
4351 		// Round to a number of deltaOrbitJDE to improve caching.
4352 		if (d != ORBIT_SEGMENTS / 2)
4353 		{
4354 			calc_date = nearbyint(calc_date / deltaOrbitJDE) * deltaOrbitJDE;
4355 		}
4356 		orbit[d] = getEclipticPos(calc_date) + parentPos;
4357 	}
4358 }
4359 
4360 // draw orbital path of Planet
drawOrbit(const StelCore * core)4361 void Planet::drawOrbit(const StelCore* core)
4362 {
4363 	if (!static_cast<bool>(orbitFader.getInterstate()))
4364 		return;
4365 	if (!static_cast<bool>(siderealPeriod))
4366 		return;
4367 	if (hidden || (pType==isObserver)) return;
4368 	if (orbitPtr && pType>=isArtificial)
4369 	{
4370 		if (!hasValidPositionalData(lastJDE, PositionQuality::OrbitPlotting))
4371 			return;
4372 	}
4373 
4374 	// Update the orbit positions to the current planet date.
4375 	computeOrbit();
4376 
4377 	const StelProjectorP prj = core->getProjection(StelCore::FrameHeliocentricEclipticJ2000);
4378 
4379 	StelPainter sPainter(prj);
4380 	const float ppx = static_cast<float>(sPainter.getProjector()->getDevicePixelsPerPixel());
4381 
4382 	// Normal transparency mode
4383 	sPainter.setBlending(true);
4384 
4385 	sPainter.setColor(getCurrentOrbitColor(), orbitFader.getInterstate());
4386 	Vec3d onscreen;
4387 	// special case - use current Planet position as center vertex so that draws
4388 	// on its orbit all the time (since segmented rather than smooth curve)
4389 	Vec3d savePos = orbit[ORBIT_SEGMENTS/2];
4390 	orbit[ORBIT_SEGMENTS/2]=getHeliocentricEclipticPos()+aberrationPush;
4391 	orbit[ORBIT_SEGMENTS]=orbit[0];
4392 	int nbIter = closeOrbit ? ORBIT_SEGMENTS : ORBIT_SEGMENTS-1;
4393 	QVarLengthArray<float, 1024> vertexArray;
4394 
4395 	sPainter.enableClientStates(true, false, false);
4396 	if (orbitsThickness>1 || ppx>1.f)
4397 		sPainter.setLineWidth(orbitsThickness*ppx);
4398 
4399 	for (int n=0; n<=nbIter; ++n)
4400 	{
4401 		if (prj->project(orbit[n],onscreen) && (vertexArray.size()==0 || !prj->intersectViewportDiscontinuity(orbit[n-1], orbit[n])))
4402 		{
4403 			vertexArray.append(static_cast<float>(onscreen[0]));
4404 			vertexArray.append(static_cast<float>(onscreen[1]));
4405 		}
4406 		else if (!vertexArray.isEmpty())
4407 		{
4408 			sPainter.setVertexPointer(2, GL_FLOAT, vertexArray.constData());
4409 			sPainter.drawFromArray(StelPainter::LineStrip, vertexArray.size()/2, 0, false);
4410 			vertexArray.clear();
4411 		}
4412 	}
4413 	orbit[ORBIT_SEGMENTS/2]=savePos;
4414 	if (!vertexArray.isEmpty())
4415 	{
4416 		sPainter.setVertexPointer(2, GL_FLOAT, vertexArray.constData());
4417 		sPainter.drawFromArray(StelPainter::LineStrip, vertexArray.size()/2, 0, false);
4418 	}
4419 	sPainter.enableClientStates(false);
4420 	if (orbitsThickness>1 || ppx>1.f)
4421 		sPainter.setLineWidth(1);
4422 }
4423 
hasValidPositionalData(const double JDE,const PositionQuality purpose) const4424 bool Planet::hasValidPositionalData(const double JDE, const PositionQuality purpose) const
4425 {
4426     if ((pType<isObserver) || (englishName=="Pluto"))
4427 		return true;
4428 	else if (orbitPtr && pType>=isArtificial)
4429 	{
4430 		switch (purpose) {
4431 		    case Position:
4432 			return static_cast<KeplerOrbit*>(orbitPtr)->objectDateValid(JDE);
4433 		    case OrbitPlotting:
4434 			return static_cast<KeplerOrbit*>(orbitPtr)->objectDateGoodEnoughForOrbits(JDE);
4435 		}
4436 	}
4437 	return false;
4438 }
4439 
getValidPositionalDataRange(const PositionQuality purpose) const4440 Vec2d Planet::getValidPositionalDataRange(const PositionQuality purpose) const
4441 {
4442 	double min=std::numeric_limits<double>::min();
4443 	double max=std::numeric_limits<double>::max();
4444 
4445 	if (orbitPtr && pType>=isArtificial)
4446 	{
4447 		return static_cast<KeplerOrbit*>(orbitPtr)->objectDateValidRange(purpose==Planet::PositionQuality::Position);
4448 	}
4449 	return Vec2d(min, max);
4450 }
4451 
update(int deltaTime)4452 void Planet::update(int deltaTime)
4453 {
4454 	hintFader.update(deltaTime);
4455 	labelsFader.update(deltaTime);
4456 	orbitFader.update(deltaTime);
4457 }
4458 
setApparentMagnitudeAlgorithm(QString algorithm)4459 void Planet::setApparentMagnitudeAlgorithm(QString algorithm)
4460 {
4461 	// sync default value with ViewDialog and SolarSystem!
4462 	vMagAlgorithm = vMagAlgorithmMap.key(algorithm, Planet::MallamaHilton_2018);
4463 }
4464 
4465 
4466 // Source: Meeus, Astronomical Algorithms, 2nd ed. 1998, ch.15, but with considerable changes.
4467 // We don't compute positions for midnights, but only for two extra positions 1 JD before and after "now", to allow interpolation of positions.
4468 // Also, the estimate h0 for the Moon in the literature is based on geocentric computation.
4469 // NOTE: Limitation for efficiency: If this is a planet moon from another planet, we compute RTS for the parent planet instead!
getRTSTime(const StelCore * core,const double altitude) const4470 Vec4d Planet::getRTSTime(const StelCore *core, const double altitude) const
4471 {
4472 	const StelLocation loc=core->getCurrentLocation();
4473 	if (loc.name.contains("->")) // a spaceship
4474 		return Vec4d(0., 0., 0., -1000.);
4475 
4476 	//StelObjectMgr* omgr=GETSTELMODULE(StelObjectMgr);
4477 	double ho = 0.;
4478 	if ( (getEnglishName()=="Moon") && (loc.planetName=="Earth")) // && core->getUseTopocentricCoordinates())
4479 		//ho = +0.7275*asin(6378.14/(eclipticPos.length()*AU)); // horizon parallax factor. This is needed for tabulations, but we must do something else.
4480 		//ho = -0.25*asin(6378.14/(eclipticPos.length()*AU)); // horizon parallax factor.
4481 		ho = - getAngularSize(core) * M_PI_180; // semidiameter;
4482 	else if (getEnglishName()=="Sun")
4483 		ho = - getAngularSize(core) * M_PI_180; // semidiameter; Canonical value 16', but this is accurate even from other planets...
4484 
4485 	if (core->getSkyDrawer()->getFlagHasAtmosphere())
4486 	{
4487 		// canonical" refraction at horizon is -34'. Replace by pressure-dependent value here!
4488 		Refraction refraction=core->getSkyDrawer()->getRefraction();
4489 		Vec3d zeroAlt(1.0,0.0,0.0);
4490 		refraction.backward(zeroAlt);
4491 		ho += asin(zeroAlt[2]);
4492 	}
4493 	if (altitude != 0.)
4494 		ho = altitude*M_PI_180; // Not sure if we use refraction for off-zero settings?
4495 	const double phi = static_cast<double>(loc.latitude) * M_PI_180;
4496 	const double L = static_cast<double>(loc.longitude) * M_PI_180; // OUR longitude. Meeus has it reversed
4497 	PlanetP obsPlanet = core->getCurrentPlanet();
4498 	const double rotRate = obsPlanet->getSiderealDay();
4499 
4500 	// We have coordinates for now and compute for previous day (JD-1) and next day (JD+1). For efficiency, we do not move the SolarSystem, but call the specific ephemeris functions.
4501 
4502 	const double currentJD=core->getJD();
4503 	const double currentJDE=core->getJDE();
4504 
4505 	// 2. compute observer planet's and target planet's ecliptical positions for JDE+/-1. (Ignore velocities)
4506 	Vec3d obs1(0.), obs3(0.), body1, body3, dummy;
4507 	if (! ((pType==isMoon) && (obsPlanet==parent)))
4508 	{
4509 		obsPlanet->computePosition(currentJDE-1., obs1, dummy);
4510 		obsPlanet->computePosition(currentJDE+1., obs3, dummy);
4511 	}
4512 	// For light time correction, we use getDistance() on the target planet and assume there is not much change from yesterday to tomorrow.
4513 	const double distanceCorrection=getDistance() * (AU / (SPEED_OF_LIGHT * 86400.));
4514 	// Limitation for efficiency: If this is a planet moon from another planet, we compute RTS for the parent planet instead!
4515 	if ((pType==isMoon) && (obsPlanet!=parent))
4516 	{
4517 		parent->computePosition(currentJDE-distanceCorrection-1., body1, dummy);
4518 		parent->computePosition(currentJDE-distanceCorrection+1., body3, dummy);
4519 	}
4520 	else
4521 	{
4522 		computePosition(currentJDE-distanceCorrection-1., body1, dummy);
4523 		computePosition(currentJDE-distanceCorrection+1., body3, dummy);
4524 	}
4525 
4526 	// And convert to equatorial coordinates of date. We can also use this day's current aberration, given the other uncertainties/omissions.
4527 	const Vec3d eq_1=core->j2000ToEquinoxEqu(StelCore::matVsop87ToJ2000.multiplyWithoutTranslation(body1+aberrationPush-obs1), StelCore::RefractionOff);
4528 	const Vec3d eq_2=getEquinoxEquatorialPos(core);
4529 	const Vec3d eq_3=core->j2000ToEquinoxEqu(StelCore::matVsop87ToJ2000.multiplyWithoutTranslation(body3+aberrationPush-obs3), StelCore::RefractionOff);
4530 	double ra1, ra2, ra3, de1, de2, de3;
4531 	StelUtils::rectToSphe(&ra1, &de1, eq_1);
4532 	StelUtils::rectToSphe(&ra2, &de2, eq_2);
4533 	StelUtils::rectToSphe(&ra3, &de3, eq_3);
4534 	// Around ra~12 there may be a jump between 12h and -12h which could crash interpolation. We better make sure to have either negative RA or RA>24 in this case.
4535 	if (cos(ra2)<0.)
4536 	{
4537 		ra1=StelUtils::fmodpos(ra1, 2*M_PI);
4538 		ra2=StelUtils::fmodpos(ra2, 2*M_PI);
4539 		ra3=StelUtils::fmodpos(ra3, 2*M_PI);
4540 	}
4541 
4542 	// 3. Approximate times:
4543 	// Sidereal Time of Place
4544 	const double Theta2=obsPlanet->getSiderealTime(currentJD, currentJDE) * (M_PI/180.) + L;  // [radians]
4545 	double cosH0=(sin(ho)-sin(phi)*sin(de2))/(cos(phi)*cos(de2));
4546 
4547 	//omgr->removeExtraInfoStrings(StelObject::DebugAid);
4548 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>1</sub>: %1=%2 &delta;<sub>1</sub>: %3<br/>").arg(QString::number(ra1, 'f', 4)).arg(StelUtils::radToHmsStr(ra1)).arg(StelUtils::radToDmsStr(de1)));
4549 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>2</sub>: %1=%2 &delta;<sub>2</sub>: %3<br/>").arg(QString::number(ra2, 'f', 4)).arg(StelUtils::radToHmsStr(ra2)).arg(StelUtils::radToDmsStr(de2)));
4550 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>3</sub>: %1=%2 &delta;<sub>3</sub>: %3<br/>").arg(QString::number(ra3, 'f', 4)).arg(StelUtils::radToHmsStr(ra3)).arg(StelUtils::radToDmsStr(de3)));
4551 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>0</sub>= %1<br/>").arg(StelUtils::radToDmsStr(ho)));
4552 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("JD<sub>2</sub>= %1<br/>").arg(QString::number(currentJD, 'f', 5)));
4553 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&Theta;<sub>2</sub>= %1<br/>").arg(StelUtils::radToHmsStr(Theta2)));
4554 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("cos H<sub>0</sub>= %1<br/>").arg(QString::number(cosH0, 'f', 4)));
4555 
4556 	double h2=StelUtils::fmodpos(Theta2-ra2, 2.*M_PI); if (h2>M_PI) h2-=2.*M_PI; // Hour angle at currentJD. This should be [-pi, pi]
4557 	// Find approximation of transit time
4558 	//double JDt=currentJD-h2/(M_PI*2.)*rotRate;
4559 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>2</sub>= %1<br/>").arg(QString::number(h2, 'f', 4)));
4560 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("JD<sub>t</sub>= %1<br/>").arg(QString::number(JDt, 'f', 4)));
4561 
4562 
4563 	// In terms of chapter 15, where m0, m1 and m2 are fractions of day within the current day, we use mr, mt, ms as fractions of day from currentJD, and they lie within [-1...+1].
4564 
4565 	double mr, ms, flag=0.;
4566 	double mt=-h2*(0.5*rotRate/M_PI);
4567 
4568 	// circumpolar: set rise and set times to lower culmination, i.e. 1/2 rotation from transit. For permanently invisible objects, set to upper culmination
4569 	if (fabs(cosH0)>1.)
4570 	{
4571 		flag = (cosH0<-1.) ? 100 : -100; // circumpolar / never rises
4572 		mr   = (cosH0<-1.) ? mt-0.5*rotRate : mt;
4573 		ms   = (cosH0<-1.) ? mt+0.5*rotRate : mt;
4574 	}
4575 	else
4576 	{
4577 		const double H0 = acos(cosH0);
4578 		//omgr->addToExtraInfoString(StelObject::DebugAid, QString("H<sub>0</sub>= %1<br/>").arg(QString::number(H0*M_180_PI, 'f', 6)));
4579 
4580 		mr = mt - H0*rotRate/(2.*M_PI);
4581 		ms = mt + H0*rotRate/(2.*M_PI);
4582 	}
4583 
4584 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>t</sub>= %1<br/>").arg(QString::number(mt, 'f', 6)));
4585 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>r</sub>= %1<br/>").arg(QString::number(mr, 'f', 6)));
4586 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>s</sub>= %1<br/>").arg(QString::number(ms, 'f', 6)));
4587 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("rise    ~ %1<br/>").arg(StelUtils::julianDayToISO8601String(currentJD+mr)));
4588 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("transit ~ %1<br/>").arg(StelUtils::julianDayToISO8601String(currentJD+mt)));
4589 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("set     ~ %1<br/>").arg(StelUtils::julianDayToISO8601String(currentJD+ms)));
4590 
4591 	// 4. Find correction for transit:
4592 	double ra_mt=StelUtils::interpolate3(mt, ra1, ra2, ra3);
4593 	double ht=StelUtils::fmodpos(Theta2-ra_mt, 2.*M_PI); if (ht>M_PI) ht-=2.*M_PI; // Hour angle of the transit RA at currentJD. This should be [-pi, pi]
4594 	mt=-ht*(0.5*rotRate/M_PI); // moment in units of day from currentJD
4595 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>t</sub>': %1=%2 <br/>").arg(QString::number(ra_mt, 'f', 4)).arg(StelUtils::radToHmsStr(ra_mt, true)));
4596 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>t</sub>': %1 = %2<br/>").arg(QString::number(ht, 'f', 6)).arg(StelUtils::radToHmsStr(ht, true)));
4597 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>t</sub>' = %1<br/>").arg(QString::number(mt, 'f', 6)));
4598 
4599 	ra_mt=StelUtils::interpolate3(mt, ra1, ra2, ra3);
4600 	ht=StelUtils::fmodpos(Theta2-ra_mt, 2.*M_PI); if (ht>M_PI) ht-=2.*M_PI; // Hour angle of the transit RA at currentJD. This should be [-pi, pi]
4601 	mt=-ht*(0.5*rotRate/M_PI); // moment in units of day from currentJD
4602 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>t</sub>'': %1=%2 <br/>").arg(QString::number(ra_mt, 'f', 4)).arg(StelUtils::radToHmsStr(ra_mt, true)));
4603 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>t</sub>'': %1 = %2<br/>").arg(QString::number(ht, 'f', 6)).arg(StelUtils::radToHmsStr(ht, true)));
4604 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>t</sub>'' = %1<br/>").arg(QString::number(mt, 'f', 6)));
4605 
4606 	// 5. Find corrections for rise and set
4607 	if (fabs(cosH0)<1.)
4608 	{
4609 		// RISE
4610 		int iterations=0; // add this to limit the loops, just in case.
4611 		double Delta_mr=1.;
4612 		while (Delta_mr > 1./8640.) // Do that until accurate to 10 seconds
4613 		{
4614 			const double theta_mr=obsPlanet->getSiderealTime(currentJD+mr, currentJDE+mr) * (M_PI/180.) + L;  // [radians]; // radians
4615 			const double ra_mr=StelUtils::interpolate3(mr, ra1, ra2, ra3);
4616 			const double de_mr=StelUtils::interpolate3(mr, de1, de2, de3);
4617 			double hr=StelUtils::fmodpos(theta_mr-ra_mr, 2.*M_PI); if (hr>M_PI) hr-=2.*M_PI; // Hour angle of the rising RA at currentJD. This should be [-pi, pi]
4618 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>r</sub>': %1=%2 <br/>").arg(QString::number(ra_mr, 'f', 4)).arg(StelUtils::radToHmsStr(ra_mr, true)));
4619 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>r</sub>': %1 = %2<br/>").arg(QString::number(hr, 'f', 6)).arg(StelUtils::radToHmsStr(hr, true)));
4620 
4621 			double ar=asin(sin(phi)*sin(de_mr)+cos(phi)*cos(de_mr)*cos(hr)); // altitude at this hour angle
4622 
4623 			Delta_mr= (ar-ho)/(cos(de_mr)*cos(phi)*sin(hr)) / (M_PI*2.);
4624 			Delta_mr=StelUtils::fmodpos(Delta_mr+0.5, 1.0)-0.5; // ensure this is a small correction
4625 			mr+=Delta_mr;
4626 
4627 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("alt<sub>r</sub>': %1 = %2<br/>").arg(QString::number(ar, 'f', 6)).arg(StelUtils::radToDmsStr(ar)));
4628 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&Delta;<sub>mr</sub>'= %1<br/>").arg(QString::number(Delta_mr, 'f', 6)));
4629 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>r</sub>' = %1<br/>").arg(QString::number(mr, 'f', 6)));
4630 
4631 			if (++iterations >= 5)
4632 				break;
4633 		}
4634 		// SET
4635 		iterations=0; // add this to limit the loops, just in case.
4636 		double Delta_ms=1.;
4637 		while (Delta_ms > 1./8640.) // Do that until accurate to 10 seconds
4638 		{
4639 			const double theta_ms=obsPlanet->getSiderealTime(currentJD+ms, currentJDE+ms) * (M_PI/180.) + L;  // [radians]; // radians
4640 			const double ra_ms=StelUtils::interpolate3(ms, ra1, ra2, ra3);
4641 			const double de_ms=StelUtils::interpolate3(ms, de1, de2, de3);
4642 			double hs=StelUtils::fmodpos(theta_ms-ra_ms, 2.*M_PI); if (hs>M_PI) hs-=2.*M_PI; // Hour angle of the setting RA at currentJD. This should be [-pi, pi]
4643 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>s</sub>': %1=%2 <br/>").arg(QString::number(ra_ms, 'f', 4)).arg(StelUtils::radToHmsStr(ra_ms, true)));
4644 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>s</sub>': %1 = %2<br/>").arg(QString::number(hs, 'f', 6)).arg(StelUtils::radToHmsStr(hs, true)));
4645 
4646 			double as=asin(sin(phi)*sin(de_ms)+cos(phi)*cos(de_ms)*cos(hs)); // altitude at this hour angle
4647 
4648 			Delta_ms= (as-ho)/(cos(de_ms)*cos(phi)*sin(hs)) / (M_PI*2.);
4649 			Delta_ms=StelUtils::fmodpos(Delta_ms+0.5, 1.0)-0.5; // ensure this is a small correction
4650 			ms+=Delta_ms;
4651 
4652 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("alt<sub>s</sub>': %1 = %2<br/>").arg(QString::number(as, 'f', 6)).arg(StelUtils::radToDmsStr(as)));
4653 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&Delta;<sub>ms</sub>'= %1<br/>").arg(QString::number(Delta_ms, 'f', 6)));
4654 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>s</sub>' = %1<br/>").arg(QString::number(ms, 'f', 6)));
4655 
4656 			if (++iterations >= 5)
4657 				break;
4658 		}
4659 	}
4660 
4661 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("rise    = %1<br/>").arg(StelUtils::julianDayToISO8601String(currentJD+mr)));
4662 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("transit = %1<br/>").arg(StelUtils::julianDayToISO8601String(currentJD+mt)));
4663 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("set     = %1<br/>").arg(StelUtils::julianDayToISO8601String(currentJD+ms)));
4664 
4665 	return Vec4d(currentJD+mr, currentJD+mt, currentJD+ms, flag);
4666 }
4667