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 λ=" << StelUtils::radToDmsStr(StelUtils::fmodpos(lon, 2.*M_PI)) << " β=" << 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α=%2 dδ=%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 (Δλ<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 — %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 ρ: %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\">φ<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\">φ<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 φ<sub>e</sub>: %4<br/>").arg(q_("Center point"), lngSystem, subearthLStr, subearthBStr);
1233 oss << QString("%1: L<sub>%2s</sub>=%3 φ<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: α: %1 δ: %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: α=%1=%2, δ=%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>: α=%1=%2, δ=%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("Δr: α=%1=%2, δ=%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("φ<sub>e</sub>: %1, φ'<sub>e</sub>: %2, λ<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("φ<sub>s</sub>: %1, φ'<sub>s</sub>: %2, λ<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("α<sub>1</sub>: %1=%2 δ<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("α<sub>2</sub>: %1=%2 δ<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("α<sub>3</sub>: %1=%2 δ<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("Θ<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("α<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("α<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("α<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("Δ<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("α<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("Δ<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