1 /*
2  * Stellarium
3  * Copyright (C) 2002 Fabien Chereau
4  * Copyright (C) 2011 Alexander Wolf
5  * Copyright (C) 2015 Georg Zotti
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
20  */
21 
22 #include "Nebula.hpp"
23 #include "NebulaMgr.hpp"
24 #include "StelTexture.hpp"
25 
26 #include "StelUtils.hpp"
27 #include "StelApp.hpp"
28 #include "StelTextureMgr.hpp"
29 #include "StelModuleMgr.hpp"
30 #include "StelCore.hpp"
31 #include "StelPainter.hpp"
32 #include "SolarSystem.hpp"
33 #include "RefractionExtinction.hpp"
34 
35 #include <QTextStream>
36 #include <QFile>
37 #include <QString>
38 #include <QRegularExpression>
39 
40 #include <QDebug>
41 #include <QBuffer>
42 
43 const QString Nebula::NEBULA_TYPE = QStringLiteral("Nebula");
44 
45 StelTextureSP Nebula::texCircle;
46 StelTextureSP Nebula::texCircleLarge;
47 StelTextureSP Nebula::texRegion;
48 StelTextureSP Nebula::texGalaxy;
49 StelTextureSP Nebula::texGalaxyLarge;
50 StelTextureSP Nebula::texOpenCluster;
51 StelTextureSP Nebula::texOpenClusterLarge;
52 StelTextureSP Nebula::texOpenClusterXLarge;
53 StelTextureSP Nebula::texGlobularCluster;
54 StelTextureSP Nebula::texGlobularClusterLarge;
55 StelTextureSP Nebula::texPlanetaryNebula;
56 StelTextureSP Nebula::texDiffuseNebula;
57 StelTextureSP Nebula::texDiffuseNebulaLarge;
58 StelTextureSP Nebula::texDiffuseNebulaXLarge;
59 StelTextureSP Nebula::texDarkNebula;
60 StelTextureSP Nebula::texDarkNebulaLarge;
61 StelTextureSP Nebula::texOpenClusterWithNebulosity;
62 StelTextureSP Nebula::texOpenClusterWithNebulosityLarge;
63 bool  Nebula::drawHintProportional = false;
64 bool  Nebula::surfaceBrightnessUsage = false;
65 bool  Nebula::designationUsage = false;
66 float Nebula::hintsBrightness = 0.f;
67 Vec3f Nebula::labelColor = Vec3f(0.4f,0.3f,0.5f);
68 QMap<Nebula::NebulaType, Vec3f>Nebula::hintColorMap;
69 QMap<Nebula::NebulaType, QString> Nebula::typeStringMap;
70 bool Nebula::flagUseTypeFilters = false;
71 Nebula::CatalogGroup Nebula::catalogFilters = Nebula::CatalogGroup(Q_NULLPTR);
72 Nebula::TypeGroup Nebula::typeFilters = Nebula::TypeGroup(Nebula::AllTypes);
73 bool Nebula::flagUseArcsecSurfaceBrightness = false;
74 bool Nebula::flagUseShortNotationSurfaceBrightness = true;
75 bool Nebula::flagUseOutlines = false;
76 bool Nebula::flagShowAdditionalNames = true;
77 bool Nebula::flagUseSizeLimits = false;
78 double Nebula::minSizeLimit = 1.0;
79 double Nebula::maxSizeLimit = 600.0;
80 
Nebula()81 Nebula::Nebula()
82 	: StelObject()
83 	, DSO_nb(0)
84 	, M_nb(0)
85 	, NGC_nb(0)
86 	, IC_nb(0)
87 	, C_nb(0)
88 	, B_nb(0)
89 	, Sh2_nb(0)
90 	, VdB_nb(0)
91 	, RCW_nb(0)
92 	, LDN_nb(0)
93 	, LBN_nb(0)
94 	, Cr_nb(0)
95 	, Mel_nb(0)
96 	, PGC_nb(0)
97 	, UGC_nb(0)
98 	, Arp_nb(0)
99 	, VV_nb(0)
100 	, DWB_nb(0)
101 	, Tr_nb(0)
102 	, St_nb(0)
103 	, Ru_nb(0)
104 	, VdBHa_nb(0)
105 	, Ced_nb("")
106 	, PK_nb("")
107 	, PNG_nb("")
108 	, SNRG_nb("")
109 	, ACO_nb("")
110 	, HCG_nb("")
111 	, ESO_nb("")
112 	, VdBH_nb("")
113 	, withoutID(false)
114 	, nameI18("")
115 	, mTypeString()
116 	, bMag(99.)
117 	, vMag(99.)
118 	, majorAxisSize(0.)
119 	, minorAxisSize(0.)
120 	, orientationAngle(0)
121 	, oDistance(0.)
122 	, oDistanceErr(0.)
123 	, redshift(99.)
124 	, redshiftErr(0.)
125 	, parallax(0.)
126 	, parallaxErr(0.)
127 	, nType()
128 {
129 	outlineSegments.clear();
130 	designations.clear();
131 }
132 
~Nebula()133 Nebula::~Nebula()
134 {
135 }
136 
getMagnitudeInfoString(const StelCore * core,const InfoStringGroup & flags,const int decimals) const137 QString Nebula::getMagnitudeInfoString(const StelCore *core, const InfoStringGroup& flags, const int decimals) const
138 {
139 	QString res;
140 	const float mmag = qMin(vMag, bMag);
141 	if (mmag < 50.f && flags&Magnitude)
142 	{
143 		QString emag = "";
144 		QString fsys = "";
145 		bool bmag = false;
146 		float mag = getVMagnitude(core);
147 		float mage = getVMagnitudeWithExtinction(core);
148 		bool hasAtmosphere = core->getSkyDrawer()->getFlagHasAtmosphere();
149 		QString tmag = q_("Magnitude");
150 		if (nType == NebDn)
151 			tmag = q_("Opacity");
152 
153 		if (bMag < 50.f && vMag > 50.f)
154 		{
155 			fsys = QString("(%1 B").arg(q_("photometric passband"));
156 			if (hasAtmosphere)
157 				fsys.append(";");
158 			else
159 				fsys.append(")");
160 			mag = getBMagnitude(core);
161 			mage = getBMagnitudeWithExtinction(core);
162 			bmag = true;
163 		}
164 
165 		const float airmass = getAirmass(core);
166 		if (nType != NebDn && airmass>-1.f) // Don't show extincted magnitude much below horizon where model is meaningless.
167 		{
168 			emag = QString("%1 <b>%2</b> %3 <b>%4</b> %5)").arg(q_("reduced to"), QString::number(mage, 'f', decimals), q_("by"), QString::number(airmass, 'f', 2), q_("Airmasses"));
169 			if (!bmag)
170 				emag = QString("(%1").arg(emag);
171 		}
172 		res = QString("%1: <b>%2</b> %3 %4<br />").arg(tmag, QString::number(mag, 'f', decimals), fsys, emag);
173 	}
174 	res += getExtraInfoStrings(Magnitude).join("");
175 	return res;
176 }
177 
getInfoString(const StelCore * core,const InfoStringGroup & flags) const178 QString Nebula::getInfoString(const StelCore *core, const InfoStringGroup& flags) const
179 {
180 	QString str;
181 	QTextStream oss(&str);
182 	bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
183 
184 	if ((flags&Name) || (flags&CatalogNumber))
185 		oss << "<h2>";
186 
187 	if (!nameI18.isEmpty() && flags&Name)
188 	{
189 		oss << getNameI18n();
190 		QString aliases = getI18nAliases();
191 		if (!aliases.isEmpty() && flagShowAdditionalNames)
192 			oss << " (" << aliases << ")";
193 	}
194 
195 	if (flags&CatalogNumber)
196 	{
197 		if (!nameI18.isEmpty() && !withoutID && flags&Name)
198 			oss << "<br>";
199 
200 		oss << designations.join(" - ");
201 	}
202 
203 	if ((flags&Name) || (flags&CatalogNumber))
204 		oss << "</h2>";
205 
206 	if (flags&Name)
207 	{
208 		QStringList extraNames=getExtraInfoStrings(Name);
209 		if (extraNames.length()>0)
210 			oss << q_("Additional names: ") << extraNames.join(", ") << "<br/>";
211 	}
212 	if (flags&CatalogNumber)
213 	{
214 		QStringList extraCat=getExtraInfoStrings(CatalogNumber);
215 		if (extraCat.length()>0)
216 			oss << q_("Additional catalog numbers: ") << extraCat.join(", ") << "<br/>";
217 	}
218 
219 	if (flags&ObjectType)
220 	{
221 		QString mt = getMorphologicalTypeString();
222 		if (mt.isEmpty())
223 			oss << QString("%1: <b>%2</b>").arg(q_("Type"), getTypeString()) << "<br>";
224 		else
225 			oss << QString("%1: <b>%2</b> (%3)").arg(q_("Type"), getTypeString(), mt) << "<br>";
226 		oss << getExtraInfoStrings(ObjectType).join("");
227 	}
228 
229 	oss << getMagnitudeInfoString(core, flags, 2);
230 
231 	if (flags&Extra)
232 	{
233 		if (vMag < 50 && bMag < 50)
234 			oss << QString("%1: <b>%2</b>").arg(q_("Color Index (B-V)"), QString::number(bMag-vMag, 'f', 2)) << "<br />";
235 	}
236 	float mmag = qMin(vMag,bMag);
237 	if (nType != NebDn && mmag < 50 && flags&Extra)
238 	{
239 		QString sb = q_("Surface brightness");
240 		QString ae = q_("after extinction");
241 		QString mu;
242 		if (flagUseShortNotationSurfaceBrightness)
243 		{
244 			mu = QString("<sup>m</sup>/□'");
245 			if (flagUseArcsecSurfaceBrightness)
246 				mu = QString("<sup>m</sup>/□\"");
247 		}
248 		else
249 		{
250 			mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-min"));
251 			if (flagUseArcsecSurfaceBrightness)
252 				mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-sec"));
253 		}
254 
255 		if (getSurfaceBrightness(core)<99.f)
256 		{
257 			if (getAirmass(core)>-1.f && getSurfaceBrightnessWithExtinction(core)<99.f) // Don't show extincted surface brightness much below horizon where model is meaningless.
258 			{
259 				oss << QString("%1: <b>%2</b> %5 (%3: <b>%4</b> %5)").arg(sb, QString::number(getSurfaceBrightness(core, flagUseArcsecSurfaceBrightness), 'f', 2),
260 											  ae, QString::number(getSurfaceBrightnessWithExtinction(core, flagUseArcsecSurfaceBrightness), 'f', 2), mu) << "<br />";
261 			}
262 			else
263 				oss << QString("%1: <b>%2</b> %3").arg(sb, QString::number(getSurfaceBrightness(core, flagUseArcsecSurfaceBrightness), 'f', 2), mu) << "<br />";
264 
265 			if (getContrastIndex(core)<99.f)
266 				oss << QString("%1: %2").arg(q_("Contrast index"), QString::number(getContrastIndex(core), 'f', 2)) << "<br />";
267 		}
268 	}
269 
270 	oss << getCommonInfoString(core, flags);
271 
272 	if (flags&Size && majorAxisSize>0.f)
273 	{
274 		QString majorAxS, minorAxS, sizeAx = q_("Size");
275 		if (withDecimalDegree)
276 		{
277 			majorAxS = StelUtils::radToDecDegStr(static_cast<double>(majorAxisSize)*M_PI/180., 5, false, true);
278 			minorAxS = StelUtils::radToDecDegStr(static_cast<double>(minorAxisSize)*M_PI/180., 5, false, true);
279 		}
280 		else
281 		{
282 			majorAxS = StelUtils::radToDmsPStr(static_cast<double>(majorAxisSize)*M_PI/180., 2);
283 			minorAxS = StelUtils::radToDmsPStr(static_cast<double>(minorAxisSize)*M_PI/180., 2);
284 		}
285 
286 		if (fuzzyEquals(majorAxisSize, minorAxisSize) || minorAxisSize==0.f)
287 			oss << QString("%1: %2").arg(sizeAx, majorAxS) << "<br />";
288 		else
289 		{
290 			oss << QString("%1: %2 x %3").arg(sizeAx, majorAxS, minorAxS) << "<br />";
291 			if (orientationAngle>0)
292 				oss << QString("%1: %2%3").arg(q_("Orientation angle")).arg(orientationAngle).arg(QChar(0x00B0)) << "<br />";
293 		}
294 	}
295 	if (flags&Size)
296 		oss << getExtraInfoStrings(Size).join("");
297 
298 	if (flags&Distance)
299 	{
300 		float distance, distanceErr, distanceLY, distanceErrLY;
301 		if (qAbs(parallax)>0.f)
302 		{
303 			QString dx;
304 			// distance in light years from parallax
305 			distance = 3.162e-5f/(qAbs(parallax)*4.848e-9f);
306 			distanceErr = 0.f;
307 
308 			if (parallaxErr>0.f)
309 				distanceErr = qAbs(3.162e-5f/(qAbs(parallaxErr + parallax)*4.848e-9f) - distance);
310 
311 			if (distanceErr>0.f)
312 				dx = QString("%1%2%3").arg(QString::number(distance, 'f', 3)).arg(QChar(0x00B1)).arg(QString::number(distanceErr, 'f', 3));
313 			else
314 				dx = QString("%1").arg(QString::number(distance, 'f', 3));
315 
316 			if (oDistance==0.f)
317 			{
318 				// TRANSLATORS: Unit of measure for distance - Light Years
319 				QString ly = qc_("ly", "distance");
320 				oss << QString("%1: %2 %3").arg(q_("Distance"), dx, ly) << "<br />";
321 			}
322 		}
323 
324 		if (oDistance>0.f)
325 		{
326 			QString dx, dy;
327 			float dc = 3262.f;
328 			int ms = 1;
329 			//TRANSLATORS: Unit of measure for distance - kiloparsecs
330 			QString dupc = qc_("kpc", "distance");
331 			//TRANSLATORS: Unit of measure for distance - Light Years
332 			QString duly = qc_("ly", "distance");
333 
334 			distance = oDistance;
335 			distanceErr = oDistanceErr;
336 			distanceLY = oDistance*dc;
337 			distanceErrLY= oDistanceErr*dc;
338 			if (oDistance>=1000.f)
339 			{
340 				distance = oDistance/1000.f;
341 				distanceErr = oDistanceErr/1000.f;
342 				//TRANSLATORS: Unit of measure for distance - Megaparsecs
343 				dupc = qc_("Mpc", "distance");
344 			}
345 
346 			if (distanceLY>=1e6f)
347 			{
348 				distanceLY /= 1e6f;
349 				distanceErrLY /= 1e6f;
350 				ms = 3;
351 				//TRANSLATORS: Unit of measure for distance - Millions of Light Years
352 				duly = qc_("M ly", "distance");
353 			}
354 
355 			if (oDistanceErr>0.f)
356 			{
357 				dx = QString("%1%2%3").arg(QString::number(distance, 'f', 3)).arg(QChar(0x00B1)).arg(QString::number(distanceErr, 'f', 3));
358 				dy = QString("%1%2%3").arg(QString::number(distanceLY, 'f', ms)).arg(QChar(0x00B1)).arg(QString::number(distanceErrLY, 'f', ms));
359 			}
360 			else
361 			{
362 				dx = QString("%1").arg(QString::number(distance, 'f', 3));
363 				dy = QString("%1").arg(QString::number(distanceLY, 'f', ms));
364 			}
365 
366 			oss << QString("%1: %2 %3 (%4 %5)").arg(q_("Distance"), dx, dupc, dy, duly) << "<br />";
367 		}
368 		oss << getExtraInfoStrings(Distance).join("");
369 	}
370 
371 	if (flags&Extra)
372 	{
373 		if (redshift<99.f)
374 		{
375 			QString z;
376 			if (redshiftErr>0.f)
377 				z = QString("%1%2%3").arg(QString::number(redshift, 'f', 6)).arg(QChar(0x00B1)).arg(QString::number(redshiftErr, 'f', 6));
378 			else
379 				z = QString("%1").arg(QString::number(redshift, 'f', 6));
380 
381 			oss << QString("%1: %2").arg(q_("Redshift"), z) << "<br />";
382 		}
383 		if (qAbs(parallax)>0.f)
384 		{
385 			QString px;
386 			if (parallaxErr>0.f)
387 				px = QString("%1%2%3").arg(QString::number(qAbs(parallax), 'f', 3)).arg(QChar(0x00B1)).arg(QString::number(parallaxErr, 'f', 3));
388 			else
389 				px = QString("%1").arg(QString::number(qAbs(parallax), 'f', 3));
390 
391 			oss << QString("%1: %2 %3").arg(q_("Parallax"), px, qc_("mas", "parallax")) << "<br />";
392 		}
393 		if (!getMorphologicalTypeDescription().isEmpty())
394 			oss << QString("%1: %2.").arg(q_("Morphological description"), getMorphologicalTypeDescription()) << "<br />";
395 	}
396 
397 	oss << getSolarLunarInfoString(core, flags);
398 
399 	postProcessInfoString(str, flags);
400 
401 	return str;
402 }
403 
getInfoMap(const StelCore * core) const404 QVariantMap Nebula::getInfoMap(const StelCore *core) const
405 {
406 	QVariantMap map = StelObject::getInfoMap(core);
407 
408 	map["type"]=getTypeString(); // replace "Nebula" type by detail. This is localized. Maybe add argument such as getTypeString(bool translated=true)?
409 	map.insert("morpho", getMorphologicalTypeString());
410 	map.insert("surface-brightness", getSurfaceBrightness(core));
411 	map.insert("designations", withoutID ? QString() : designations.join(" - "));
412 	map.insert("bmag", bMag);
413 	if (vMag < 50 && bMag < 50)
414 		map.insert("bV", bMag-vMag);
415 	if (redshift<99.f)
416 		map.insert("redshift", redshift);
417 
418 	// TODO: more? Names? Data?
419 	return map;
420 }
421 
getEnglishAliases() const422 QString Nebula::getEnglishAliases() const
423 {
424 	QString aliases = "";
425 	int asize = englishAliases.size();
426 	if (asize!=0)
427 	{
428 		if (asize>2) // Special case for many AKA
429 		{
430 			bool firstLine = true;
431 			for(int i=1; i<=asize; i++)
432 			{
433 				aliases.append(englishAliases.at(i-1));
434 				if (i<asize)
435 					aliases.append(" - ");
436 
437 				if ((i % 2)==0 && firstLine) // 2 AKA-items on first line!
438 				{
439 					aliases.append("<br />");
440 					firstLine = false;
441 				}
442 				if (i>3 && ((i-2) % 4)==0 && !firstLine &&  i<asize)
443 					aliases.append("<br />");
444 			}
445 		}
446 		else
447 			aliases = nameI18Aliases.join(" - ");
448 	}
449 	return aliases;
450 }
451 
getI18nAliases() const452 QString Nebula::getI18nAliases() const
453 {
454 	QString aliases = "";
455 	int asize = nameI18Aliases.size();
456 	if (asize!=0)
457 	{
458 		if (asize>2) // Special case for many AKA; NOTE: Should we add size to the config data for skyculture?
459 		{
460 			bool firstLine = true;
461 			for(int i=1; i<=asize; i++)
462 			{
463 				aliases.append(nameI18Aliases.at(i-1));
464 				if (i<asize)
465 					aliases.append(" - ");
466 
467 				if ((i % 2)==0 && firstLine) // 2 AKA-items on first line!
468 				{
469 					aliases.append("<br />");
470 					firstLine = false;
471 				}
472 				if (i>3 && ((i-2) % 4)==0 && !firstLine &&  i<asize)
473 						aliases.append("<br />");
474 			}
475 		}
476 		else
477 			aliases = nameI18Aliases.join(" - ");
478 	}
479 	return aliases;
480 }
481 
getVMagnitude(const StelCore * core) const482 float Nebula::getVMagnitude(const StelCore* core) const
483 {
484 	Q_UNUSED(core)
485 	return vMag;
486 }
487 
getBMagnitude(const StelCore * core) const488 float Nebula::getBMagnitude(const StelCore* core) const
489 {
490 	Q_UNUSED(core)
491 	return bMag;
492 }
493 
getBMagnitudeWithExtinction(const StelCore * core) const494 float Nebula::getBMagnitudeWithExtinction(const StelCore* core) const
495 {
496 	Vec3d altAzPos = getAltAzPosGeometric(core);
497 	altAzPos.normalize();
498 	float mag = getBMagnitude(core);
499 	// without the test, planets flicker stupidly in fullsky atmosphere-less view.
500 	if (core->getSkyDrawer()->getFlagHasAtmosphere())
501 		core->getSkyDrawer()->getExtinction().forward(altAzPos, &mag);
502 	return mag;
503 }
504 
getAngularSize(const StelCore *) const505 double Nebula::getAngularSize(const StelCore *) const
506 {
507 	float size = majorAxisSize;
508 	if (!fuzzyEquals(majorAxisSize, minorAxisSize) || minorAxisSize>0)
509 		size = (majorAxisSize+minorAxisSize)*0.5f;
510 	return static_cast<double>(size);
511 }
512 
getSelectPriority(const StelCore * core) const513 float Nebula::getSelectPriority(const StelCore* core) const
514 {
515 	float selectPriority = StelObject::getSelectPriority(core);
516 	const NebulaMgr* nebMgr = (static_cast<NebulaMgr*>(StelApp::getInstance().getModuleMgr().getModule("NebulaMgr")));
517 	// minimize unwanted selection of the deep-sky objects
518 	if (!nebMgr->getFlagHints())
519 		return selectPriority+3.f;
520 
521 	float mag = qMin(getVMagnitude(core), getBMagnitude(core));
522 	float lim = mag;
523 	float mLim = 15.0f;
524 
525 	if (nType==NebRegion) // special case for regions
526 		mag = 3.f;
527 
528 	if (!objectInDisplayedCatalog() || !objectInDisplayedType())
529 		return selectPriority+mLim;
530 
531 	const StelSkyDrawer* drawer = core->getSkyDrawer();
532 
533 	if (drawer->getFlagNebulaMagnitudeLimit() && (mag>static_cast<float>(drawer->getCustomNebulaMagnitudeLimit())))
534 		return selectPriority+mLim;
535 
536 	const float maxMagHint = nebMgr->computeMaxMagHint(drawer);
537 	// make very easy to select if labeled
538 	if (surfaceBrightnessUsage)
539 	{
540 		lim = mag = getSurfaceBrightness(core);
541 		mLim += 1.f;
542 	}
543 
544 	if (nType==NebDn)
545 		lim=mLim - mag - 2.0f*qMin(1.5f, majorAxisSize); // Note that "mag" field is used for opacity in this catalog!
546 	else if (nType==NebHII) // Sharpless and LBN
547 		lim=10.0f - 2.0f*qMin(1.5f, majorAxisSize); // Unfortunately, in Sh catalog, we always have mag=99=unknown!
548 
549 	if (std::min(mLim, lim)<=maxMagHint || outlineSegments.size()>0 || nType==NebRegion) // High priority for big DSO (with outlines) or regions
550 		selectPriority = -10.f;
551 	else
552 		selectPriority -= 5.f;
553 
554 	return selectPriority;
555 }
556 
getInfoColor(void) const557 Vec3f Nebula::getInfoColor(void) const
558 {
559 	return (static_cast<NebulaMgr*>(StelApp::getInstance().getModuleMgr().getModule("NebulaMgr")))->getLabelsColor();
560 }
561 
getCloseViewFov(const StelCore *) const562 double Nebula::getCloseViewFov(const StelCore*) const
563 {
564 	return majorAxisSize>0.f ? static_cast<double>(majorAxisSize) * 4. : 1.;
565 }
566 
getSurfaceBrightness(const StelCore * core,bool arcsec) const567 float Nebula::getSurfaceBrightness(const StelCore* core, bool arcsec) const
568 {
569 	const float sq = (arcsec ? 3600.f*3600.f : 3600.f); // arcsec^2 or arcmin^2
570 	const float mag = qMin(getVMagnitude(core), getBMagnitude(core));
571 	if (mag<99.f && majorAxisSize>0.f && nType!=NebDn)
572 		return mag + 2.5f*log10f(getSurfaceArea()*sq);
573 	else
574 		return 99.f;
575 }
576 
getSurfaceBrightnessWithExtinction(const StelCore * core,bool arcsec) const577 float Nebula::getSurfaceBrightnessWithExtinction(const StelCore* core, bool arcsec) const
578 {
579 	const float sq = (arcsec ? 3600.f*3600.f : 3600.f); // arcsec^2 or arcmin^2
580 	const float mag = qMin(getVMagnitudeWithExtinction(core), getBMagnitudeWithExtinction(core));
581 	if (mag<99.f && majorAxisSize>0.f && nType!=NebDn)
582 		return mag + 2.5f*log10f(getSurfaceArea()*sq);
583 	else
584 		return 99.f;
585 }
586 
getContrastIndex(const StelCore * core) const587 float Nebula::getContrastIndex(const StelCore* core) const
588 {
589 	// Compute an extended object's contrast index: http://www.unihedron.com/projects/darksky/NELM2BCalc.html
590 
591 	// Sky brightness
592 	// Source: Schaefer, B.E. Feb. 1990. Telescopic Limiting Magnitude. PASP 102:212-229
593 	// URL: http://adsbit.harvard.edu/cgi-bin/nph-iarticle_query?bibcode=1990PASP..102..212S [1990PASP..102..212S]
594 	const float B_mpsas = 21.58f - 5*log10(std::pow(10.f, 1.586f - static_cast<float>(core->getSkyDrawer()->getNELMFromBortleScale())*0.2f)-1);
595 	// Compute an extended object's contrast index
596 	// Source: Clark, R.N., 1990. Appendix E in Visual Astronomy of the Deep Sky, Cambridge University Press and Sky Publishing.
597 	// URL: http://www.clarkvision.com/visastro/appendix-e.html
598 	const float emag = getSurfaceBrightnessWithExtinction(core, true);
599 	if (emag<99.f)
600 		return -0.4f * (emag - B_mpsas);
601 	else
602 		return 99.f;
603 }
604 
getSurfaceArea(void) const605 float Nebula::getSurfaceArea(void) const
606 {
607 	if (minorAxisSize==0.f)
608 		return M_PIf*(majorAxisSize/2.f)*(majorAxisSize/2.f); // S = pi*R^2 = pi*(D/2)^2
609 	else
610 		return M_PIf*(majorAxisSize/2.f)*(minorAxisSize/2.f); // S = pi*a*b
611 }
612 
getHintColor(Nebula::NebulaType nType)613 Vec3f Nebula::getHintColor(Nebula::NebulaType nType)
614 {
615 	return hintColorMap.value(nType, hintColorMap.value(NebUnknown));
616 }
617 
getVisibilityLevelByMagnitude(void) const618 float Nebula::getVisibilityLevelByMagnitude(void) const
619 {
620 	StelCore* core = StelApp::getInstance().getCore();
621 
622 	float lim = qMin(vMag, bMag);
623 	float mLim = 15.0f;
624 
625 	if (surfaceBrightnessUsage)
626 	{
627 		lim = getSurfaceBrightness(core) - 3.f;
628 		if (lim > 90.f) lim = mLim + 1.f;
629 	}
630 	else
631 	{
632 		float mag = getVMagnitude(core);
633 		if (lim > 90.f) lim = mLim;
634 
635 		// Dark nebulae. Not sure how to assess visibility from opacity? --GZ
636 		if (nType==NebDn)
637 		{
638 			// GZ: ad-hoc visibility formula: assuming good visibility if objects of mag9 are visible, "usual" opacity 5 and size 30', better visibility (discernability) comes with higher opacity and larger size,
639 			// 9-(opac-5)-2*(angularSize-0.5)
640 			// GZ Not good for non-Barnards. weak opacity and large surface are antagonists. (some LDN are huge, but opacity 2 is not much to discern).
641 			// The qMin() maximized the visibility gain for large objects.
642 			if (majorAxisSize>0.f && mag<90.f)
643 				lim = mLim - mag - 2.0f*qMin(majorAxisSize, 1.5f);
644 			else
645 				lim = (B_nb>0 ? 9.0f : 12.0f); // GZ I assume LDN objects are rather elusive.
646 		}
647 		else if (nType==NebHII) // NebHII={Sharpless, LBN, RCW}
648 		{ // artificially increase visibility of (most) Sharpless objects? No magnitude recorded:-(
649 			lim=9.0f;
650 		}
651 	}
652 
653 	if (nType==NebRegion) // special case for regions
654 		lim=3.0f;
655 
656 	return lim;
657 }
658 
drawOutlines(StelPainter & sPainter,float maxMagHints) const659 void Nebula::drawOutlines(StelPainter &sPainter, float maxMagHints) const
660 {
661 	size_t segments = outlineSegments.size();
662 	Vec3f color = getHintColor(nType);
663 
664 	// tune limits for outlines
665 	float oLim = getVisibilityLevelByMagnitude() - 3.f;
666 
667 	float lum = 1.f;
668 	Vec3f col(color*lum*hintsBrightness);
669 	if (!objectInDisplayedType())
670 		col.set(0.f,0.f,0.f);
671 	sPainter.setColor(col, 1);
672 
673 	StelCore *core=StelApp::getInstance().getCore();
674 	Vec3d vel=core->getCurrentPlanet()->getHeliocentricEclipticVelocity();
675 	vel=StelCore::matVsop87ToJ2000*vel;
676 	vel*=core->getAberrationFactor() * (AU/(86400.0*SPEED_OF_LIGHT));
677 
678 	// Show outlines
679 	if (segments>0 && flagUseOutlines && oLim<=maxMagHints)
680 	{
681 		unsigned int i, j;
682 		std::vector<Vec3d> *points;
683 
684 		sPainter.setBlending(true);
685 		sPainter.setLineSmooth(true);
686 		const SphericalCap& viewportHalfspace = sPainter.getProjector()->getBoundingCap();
687 
688 		for (i=0;i<segments;i++)
689 		{
690 			points = outlineSegments[i];
691 
692 			for (j=0;j<points->size()-1;j++)
693 			{
694 				Vec3d point1=points->at(j);
695 				Vec3d point2=points->at(j+1);
696 				if (core->getUseAberration())
697 				{
698 					point1+=vel;
699 					point1.normalize();
700 					point2+=vel;
701 					point2.normalize();
702 				}
703 				sPainter.drawGreatCircleArc(point1, point2, &viewportHalfspace);
704 			}
705 		}
706 		sPainter.setLineSmooth(false);
707 	}
708 }
709 
drawHints(StelPainter & sPainter,float maxMagHints,StelCore * core) const710 void Nebula::drawHints(StelPainter& sPainter, float maxMagHints, StelCore *core) const
711 {
712 	size_t segments = outlineSegments.size();
713 	if (segments>0 && flagUseOutlines)
714 		return;
715 	Vec3d win;
716 	// Check visibility of DSO hints
717 	if (!(sPainter.getProjector()->projectCheck(XYZ, win)))
718 		return;
719 
720 	if (getVisibilityLevelByMagnitude()>maxMagHints)
721 		return;
722 
723 	Vec3f color = getHintColor(nType);
724 
725 	const float size = 6.0f;
726 	float scaledSize = 0.0f;
727 	if (drawHintProportional)
728 		scaledSize = static_cast<float>(getAngularSize(Q_NULLPTR)) *M_PI_180f*static_cast<float>(sPainter.getProjector()->getPixelPerRadAtCenter());
729 	float finalSize=qMax(size, scaledSize);
730 
731 	switch (nType)
732 	{
733 		case NebGx:
734 		case NebIGx:
735 		case NebAGx:
736 		case NebQSO:
737 		case NebPossQSO:
738 		case NebBLL:
739 		case NebBLA:
740 		case NebRGx:
741 		case NebGxCl:
742 			if (finalSize > 35.f)
743 				Nebula::texGalaxyLarge->bind();
744 			else
745 				Nebula::texGalaxy->bind();
746 			break;
747 		case NebOc:
748 		case NebSA:
749 		case NebSC:
750 		case NebCl:
751 			if (finalSize > 75.f)
752 				Nebula::texOpenClusterXLarge->bind();
753 			else if (finalSize > 35.f)
754 				Nebula::texOpenClusterLarge->bind();
755 			else
756 				Nebula::texOpenCluster->bind();
757 			break;
758 		case NebGc:
759 			if (finalSize > 35.f)
760 				Nebula::texGlobularClusterLarge->bind();
761 			else
762 				Nebula::texGlobularCluster->bind();
763 			break;
764 		case NebN:
765 		case NebHII:
766 		case NebMolCld:
767 		case NebYSO:
768 		case NebRn:
769 		case NebSNR:
770 		case NebBn:
771 		case NebEn:
772 		case NebSNC:
773 		case NebSNRC:
774 			if (finalSize > 75.f)
775 				Nebula::texDiffuseNebulaXLarge->bind();
776 			else if (finalSize > 35.f)
777 				Nebula::texDiffuseNebulaLarge->bind();
778 			else
779 				Nebula::texDiffuseNebula->bind();
780 			break;
781 		case NebPn:
782 		case NebPossPN:
783 		case NebPPN:
784 			Nebula::texPlanetaryNebula->bind();
785 			break;
786 		case NebDn:
787 			if (finalSize > 35.f)
788 				Nebula::texDarkNebulaLarge->bind();
789 			else
790 				Nebula::texDarkNebula->bind();
791 			break;
792 		case NebCn:
793 			if (finalSize > 35.f)
794 				Nebula::texOpenClusterWithNebulosityLarge->bind();
795 			else
796 				Nebula::texOpenClusterWithNebulosity->bind();
797 			break;
798 		case NebRegion:
799 			finalSize = size*2.f;
800 			Nebula::texRegion->bind();
801 			break;
802 		//case NebEMO:
803 		//case NebStar:
804 		//case NebSymbioticStar:
805 		//case NebEmissionLineStar:
806 		default:
807 			if (finalSize > 35.f)
808 				Nebula::texCircleLarge->bind();
809 			else
810 				Nebula::texCircle->bind();
811 	}
812 
813 	float lum = 1.f;
814 	Vec3f col(color*lum*hintsBrightness);
815 	if (!objectInDisplayedType())
816 		col.set(0.f,0.f,0.f);
817 
818 	sPainter.setColor(col, 1);
819 	sPainter.setBlending(true, GL_ONE, GL_ONE);
820 
821 	// Rotation looks good only for galaxies.
822 	if ((nType <=NebQSO) || (nType==NebBLA) || (nType==NebBLL) )
823 	{
824 		// The rotation angle in drawSprite2dMode() is relative to screen. Make sure to compute correct angle from 90+orientationAngle.
825 		// Find an on-screen direction vector from a point offset somewhat in declination from our object.
826 		Vec3d XYZrel(getJ2000EquatorialPos(core));
827 		XYZrel[2]*=0.95; XYZrel.normalize();
828 		Vec3d XYrel;
829 		sPainter.getProjector()->project(XYZrel, XYrel);
830 		float screenAngle = static_cast<float>(atan2(XYrel[1]-XY[1], XYrel[0]-XY[0]));
831 		sPainter.drawSprite2dMode(static_cast<float>(XY[0]), static_cast<float>(XY[1]), finalSize, screenAngle*M_180_PIf + orientationAngle);
832 	}
833 	else	// no galaxy
834 		sPainter.drawSprite2dMode(static_cast<float>(XY[0]), static_cast<float>(XY[1]), finalSize);
835 }
836 
drawLabel(StelPainter & sPainter,float maxMagLabel) const837 void Nebula::drawLabel(StelPainter& sPainter, float maxMagLabel) const
838 {
839 	Vec3d win;
840 	// Check visibility of DSO labels
841 	if (!(sPainter.getProjector()->projectCheck(XYZ, win)))
842 		return;
843 
844 	if (getVisibilityLevelByMagnitude()>maxMagLabel)
845 		return;
846 
847 	sPainter.setColor(labelColor, objectInDisplayedType() ? hintsBrightness : 0.f);
848 
849 	const float size = static_cast<float>(getAngularSize(Q_NULLPTR))*M_PI_180f*sPainter.getProjector()->getPixelPerRadAtCenter();
850 	const float shift = 5.f + (drawHintProportional ? size*0.9f : 0.f);
851 
852 	QString str = getNameI18n();
853 	if (str.isEmpty() || designationUsage)
854 		str = getDSODesignation();
855 
856 	sPainter.drawText(static_cast<float>(XY[0])+shift, static_cast<float>(XY[1])+shift, str, 0, 0, 0, false);
857 }
858 
getDSODesignation() const859 QString Nebula::getDSODesignation() const
860 {
861 	QString str = "";
862 	// Get designation for DSO with priority as given here.
863 	if (catalogFilters&CatM && M_nb>0)
864 		str = QString("M %1").arg(M_nb);
865 	else if (catalogFilters&CatC && C_nb>0)
866 		str = QString("C %1").arg(C_nb);
867 	else if (catalogFilters&CatNGC && NGC_nb>0)
868 		str = QString("NGC %1").arg(NGC_nb);
869 	else if (catalogFilters&CatIC && IC_nb>0)
870 		str = QString("IC %1").arg(IC_nb);
871 	else if (catalogFilters&CatB && B_nb>0)
872 		str = QString("B %1").arg(B_nb);
873 	else if (catalogFilters&CatSh2 && Sh2_nb>0)
874 		str = QString("SH 2-%1").arg(Sh2_nb);
875 	else if (catalogFilters&CatVdB && VdB_nb>0)
876 		str = QString("vdB %1").arg(VdB_nb);
877 	else if (catalogFilters&CatRCW && RCW_nb>0)
878 		str = QString("RCW %1").arg(RCW_nb);
879 	else if (catalogFilters&CatLDN && LDN_nb>0)
880 		str = QString("LDN %1").arg(LDN_nb);
881 	else if (catalogFilters&CatLBN && LBN_nb > 0)
882 		str = QString("LBN %1").arg(LBN_nb);
883 	else if (catalogFilters&CatCr && Cr_nb > 0)
884 		str = QString("Cr %1").arg(Cr_nb);
885 	else if (catalogFilters&CatMel && Mel_nb > 0)
886 		str = QString("Mel %1").arg(Mel_nb);
887 	else if (catalogFilters&CatPGC && PGC_nb > 0)
888 		str = QString("PGC %1").arg(PGC_nb);
889 	else if (catalogFilters&CatUGC && UGC_nb > 0)
890 		str = QString("UGC %1").arg(UGC_nb);
891 	else if (catalogFilters&CatCed && !Ced_nb.isEmpty())
892 		str = QString("Ced %1").arg(Ced_nb);
893 	else if (catalogFilters&CatArp && Arp_nb > 0)
894 		str = QString("Arp %1").arg(Arp_nb);
895 	else if (catalogFilters&CatVV && VV_nb > 0)
896 		str = QString("VV %1").arg(VV_nb);
897 	else if (catalogFilters&CatPK && !PK_nb.isEmpty())
898 		str = QString("PK %1").arg(PK_nb);
899 	else if (catalogFilters&CatPNG && !PNG_nb.isEmpty())
900 		str = QString("PN G%1").arg(PNG_nb);
901 	else if (catalogFilters&CatSNRG && !SNRG_nb.isEmpty())
902 		str = QString("SNR G%1").arg(SNRG_nb);
903 	else if (catalogFilters&CatACO && !ACO_nb.isEmpty())
904 		str = QString("Abell %1").arg(ACO_nb);
905 	else if (catalogFilters&CatHCG && !HCG_nb.isEmpty())
906 		str = QString("HCG %1").arg(HCG_nb);
907 	else if (catalogFilters&CatESO && !ESO_nb.isEmpty())
908 		str = QString("ESO %1").arg(ESO_nb);
909 	else if (catalogFilters&CatVdBH && !VdBH_nb.isEmpty())
910 		str = QString("vdBH %1").arg(VdBH_nb);
911 	else if (catalogFilters&CatDWB && DWB_nb > 0)
912 		str = QString("DWB %1").arg(DWB_nb);
913 	else if (catalogFilters&CatTr && Tr_nb > 0)
914 		str = QString("Tr %1").arg(Tr_nb);
915 	else if (catalogFilters&CatSt && St_nb > 0)
916 		str = QString("St %1").arg(St_nb);
917 	else if (catalogFilters&CatRu && Ru_nb > 0)
918 		str = QString("Ru %1").arg(Ru_nb);
919 	else if (catalogFilters&CatVdBHa && VdBHa_nb > 0)
920 		str = QString("vdB-Ha %1").arg(VdBHa_nb);
921 
922 	return str;
923 }
924 
getDSODesignationWIC() const925 QString Nebula::getDSODesignationWIC() const
926 {
927 	if (!withoutID)
928 		return designations.first();
929 	else
930 		return QString();
931 }
932 
933 
readDSO(QDataStream & in)934 void Nebula::readDSO(QDataStream &in)
935 {
936 	float	ra, dec;
937 	unsigned int oType;
938 
939 	in	>> DSO_nb >> ra >> dec >> bMag >> vMag >> oType >> mTypeString >> majorAxisSize >> minorAxisSize
940 		>> orientationAngle >> redshift >> redshiftErr >> parallax >> parallaxErr >> oDistance >> oDistanceErr
941 		>> NGC_nb >> IC_nb >> M_nb >> C_nb >> B_nb >> Sh2_nb >> VdB_nb >> RCW_nb >> LDN_nb >> LBN_nb >> Cr_nb
942 		>> Mel_nb >> PGC_nb >> UGC_nb >> Ced_nb >> Arp_nb >> VV_nb >> PK_nb >> PNG_nb >> SNRG_nb >> ACO_nb
943 		>> HCG_nb >> ESO_nb >> VdBH_nb >> DWB_nb >> Tr_nb >> St_nb >> Ru_nb >> VdBHa_nb;
944 
945 	const unsigned int f = NGC_nb + IC_nb + M_nb + C_nb + B_nb + Sh2_nb + VdB_nb + RCW_nb + LDN_nb + LBN_nb + Cr_nb + Mel_nb + PGC_nb + UGC_nb + Arp_nb + VV_nb + DWB_nb + Tr_nb + St_nb + Ru_nb + VdBHa_nb;
946 	if (f==0 && Ced_nb.isEmpty() && PK_nb.isEmpty() && PNG_nb.isEmpty() && SNRG_nb.isEmpty() && ACO_nb.isEmpty() && HCG_nb.isEmpty() && ESO_nb.isEmpty() && VdBH_nb.isEmpty())
947 		withoutID = true;
948 
949 	if (M_nb > 0) designations << QString("M %1").arg(M_nb);
950 	if (C_nb > 0)  designations << QString("C %1").arg(C_nb);
951 	if (NGC_nb > 0) designations << QString("NGC %1").arg(NGC_nb);
952 	if (IC_nb > 0) designations << QString("IC %1").arg(IC_nb);
953 	if (B_nb > 0) designations << QString("B %1").arg(B_nb);
954 	if (Sh2_nb > 0) designations << QString("SH 2-%1").arg(Sh2_nb);
955 	if (VdB_nb > 0) designations << QString("vdB %1").arg(VdB_nb);
956 	if (RCW_nb > 0) designations << QString("RCW %1").arg(RCW_nb);
957 	if (LDN_nb > 0) designations << QString("LDN %1").arg(LDN_nb);
958 	if (LBN_nb > 0) designations << QString("LBN %1").arg(LBN_nb);
959 	if (Cr_nb > 0) designations << QString("Cr %1").arg(Cr_nb);
960 	if (Mel_nb > 0) designations << QString("Mel %1").arg(Mel_nb);
961 	if (PGC_nb > 0) designations << QString("PGC %1").arg(PGC_nb);
962 	if (UGC_nb > 0) designations << QString("UGC %1").arg(UGC_nb);
963 	if (!Ced_nb.isEmpty()) designations << QString("Ced %1").arg(Ced_nb);
964 	if (Arp_nb > 0) designations << QString("Arp %1").arg(Arp_nb);
965 	if (VV_nb > 0) designations << QString("VV %1").arg(VV_nb);
966 	if (!PK_nb.isEmpty()) designations << QString("PK %1").arg(PK_nb);
967 	if (!PNG_nb.isEmpty()) designations << QString("PN G%1").arg(PNG_nb);
968 	if (!SNRG_nb.isEmpty()) designations << QString("SNR G%1").arg(SNRG_nb);
969 	if (!ACO_nb.isEmpty()) designations << QString("Abell %1").arg(ACO_nb);
970 	if (!HCG_nb.isEmpty()) designations << QString("HCG %1").arg(HCG_nb);
971 	if (!ESO_nb.isEmpty()) designations << QString("ESO %1").arg(ESO_nb);
972 	if (!VdBH_nb.isEmpty()) designations << QString("vdBH %1").arg(VdBH_nb);
973 	if (DWB_nb > 0) designations << QString("DWB %1").arg(DWB_nb);
974 	if (Tr_nb > 0) designations << QString("Tr %1").arg(Tr_nb);
975 	if (St_nb > 0) designations << QString("St %1").arg(St_nb);
976 	if (Ru_nb > 0) designations << QString("Ru %1").arg(Ru_nb);
977 	if (VdBHa_nb > 0) designations << QString("vdB-Ha %1").arg(VdBHa_nb);
978 
979 	StelUtils::spheToRect(ra,dec,XYZ);
980 	Q_ASSERT(fabs(XYZ.lengthSquared()-1.)<1e-9);
981 	nType = static_cast<Nebula::NebulaType>(oType);
982 	pointRegion = SphericalRegionP(new SphericalPoint(getJ2000EquatorialPos(Q_NULLPTR)));
983 }
984 
objectInDisplayedType() const985 bool Nebula::objectInDisplayedType() const
986 {
987 	if (!flagUseTypeFilters)
988 		return true;
989 
990 	int cntype = -1;
991 	switch (nType)
992 	{
993 		case NebGx:
994 			cntype = 0; // Galaxies
995 			break;
996 		case NebAGx:
997 		case NebRGx:
998 		case NebQSO:
999 		case NebPossQSO:
1000 		case NebBLL:
1001 		case NebBLA:
1002 			cntype = 1; // Active Galaxies
1003 			break;
1004 		case NebIGx:
1005 			cntype = 2; // Interacting Galaxies
1006 			break;
1007 		case NebOc:
1008 		case NebCl:
1009 		case NebSA:
1010 		case NebSC:
1011 			cntype = 3; // Open Star Clusters
1012 			break;
1013 		case NebHII:
1014 		case NebISM:
1015 			cntype = 4; // Hydrogen regions (include interstellar matter)
1016 			break;
1017 		case NebN:
1018 		case NebBn:
1019 		case NebEn:
1020 		case NebRn:
1021 			cntype = 5; // Bright Nebulae
1022 			break;
1023 		case NebDn:
1024 		case NebMolCld:
1025 		case NebYSO:
1026 			cntype = 6; // Dark Nebulae
1027 			break;
1028 		case NebPn:
1029 		case NebPossPN:
1030 		case NebPPN:
1031 			cntype = 7; // Planetary Nebulae
1032 			break;
1033 		case NebSNR:
1034 		case NebSNC:
1035 		case NebSNRC:
1036 			cntype = 8; // Supernova Remnants
1037 			break;
1038 		case NebCn:
1039 			cntype = 9;
1040 			break;
1041 		case NebGxCl:
1042 			cntype = 10;
1043 			break;
1044 		case NebGc:
1045 			cntype = 11;
1046 			break;
1047 		default:
1048 			cntype = 12;
1049 			break;
1050 	}
1051 	bool r = ( (typeFilters&TypeGalaxies             && cntype==0)
1052 		|| (typeFilters&TypeActiveGalaxies       && cntype==1)
1053 		|| (typeFilters&TypeInteractingGalaxies  && cntype==2)
1054 		|| (typeFilters&TypeOpenStarClusters     && cntype==3)
1055 		|| (typeFilters&TypeGlobularStarClusters && cntype==11)
1056 		|| (typeFilters&TypeHydrogenRegions      && cntype==4)
1057 		|| (typeFilters&TypeBrightNebulae        && cntype==5)
1058 		|| (typeFilters&TypeDarkNebulae          && cntype==6)
1059 		|| (typeFilters&TypePlanetaryNebulae     && cntype==7)
1060 		|| (typeFilters&TypeSupernovaRemnants    && cntype==8)
1061 		|| (typeFilters&TypeOpenStarClusters     && (typeFilters&TypeBrightNebulae || typeFilters&TypeHydrogenRegions) && cntype==9)
1062 		|| (typeFilters&TypeGalaxyClusters       && cntype==10)
1063 		|| (typeFilters&TypeOther                && cntype==12));
1064 
1065 	return r;
1066 }
1067 
objectInDisplayedCatalog() const1068 bool Nebula::objectInDisplayedCatalog() const
1069 {
1070 	bool r = ( ((catalogFilters&CatM)     && (M_nb>0))
1071 		|| ((catalogFilters&CatC)     && (C_nb>0))
1072 		|| ((catalogFilters&CatNGC)   && (NGC_nb>0))
1073 		|| ((catalogFilters&CatIC)    && (IC_nb>0))
1074 		|| ((catalogFilters&CatB)     && (B_nb>0))
1075 		|| ((catalogFilters&CatSh2)   && (Sh2_nb>0))
1076 		|| ((catalogFilters&CatVdB)   && (VdB_nb>0))
1077 		|| ((catalogFilters&CatRCW)   && (RCW_nb>0))
1078 		|| ((catalogFilters&CatLDN)   && (LDN_nb>0))
1079 		|| ((catalogFilters&CatLBN)   && (LBN_nb>0))
1080 		|| ((catalogFilters&CatCr)    && (Cr_nb>0))
1081 		|| ((catalogFilters&CatMel)   && (Mel_nb>0))
1082 		|| ((catalogFilters&CatPGC)   && (PGC_nb>0))
1083 		|| ((catalogFilters&CatUGC)   && (UGC_nb>0))
1084 		|| ((catalogFilters&CatCed)   && !(Ced_nb.isEmpty()))
1085 		|| ((catalogFilters&CatArp)   && (Arp_nb>0))
1086 		|| ((catalogFilters&CatVV)    && (VV_nb>0))
1087 		|| ((catalogFilters&CatPK)    && !(PK_nb.isEmpty()))
1088 		|| ((catalogFilters&CatPNG)   && !(PNG_nb.isEmpty()))
1089 		|| ((catalogFilters&CatSNRG)  && !(SNRG_nb.isEmpty()))
1090 		|| ((catalogFilters&CatACO)   && (!ACO_nb.isEmpty()))
1091 		|| ((catalogFilters&CatHCG)   && (!HCG_nb.isEmpty()))
1092 		|| ((catalogFilters&CatESO)   && (!ESO_nb.isEmpty()))
1093 		|| ((catalogFilters&CatVdBH)  && (!VdBH_nb.isEmpty()))
1094 		|| ((catalogFilters&CatDWB)   && (DWB_nb>0))
1095 		|| ((catalogFilters&CatTr)	   && (Tr_nb>0))
1096 		|| ((catalogFilters&CatSt)	   && (St_nb>0))
1097 		|| ((catalogFilters&CatRu	)   && (Ru_nb>0))
1098 		|| ((catalogFilters&CatVdBHa)   && (VdBHa_nb>0)))
1099 
1100 		// Special case: objects without ID from current catalogs
1101 		|| ((catalogFilters&CatOther)   && withoutID);
1102 
1103 	return r;
1104 }
1105 
objectInAllowedSizeRangeLimits(void) const1106 bool Nebula::objectInAllowedSizeRangeLimits(void) const
1107 {
1108 	bool r = true;
1109 	if (flagUseSizeLimits)
1110 	{
1111 		const double size = 60. * static_cast<double>(qMax(majorAxisSize, minorAxisSize));
1112 		r = (size>=minSizeLimit && size<=maxSizeLimit);
1113 	}
1114 	return r;
1115 }
1116 
getMorphologicalTypeString(void) const1117 QString Nebula::getMorphologicalTypeString(void) const
1118 {
1119 	return mTypeString;
1120 }
1121 
getMorphologicalTypeDescription(void) const1122 QString Nebula::getMorphologicalTypeDescription(void) const
1123 {
1124 	QString m, r = "";
1125 
1126 	// Let's avoid showing a wrong morphological description for galaxies
1127 	// NOTE: Is required the morphological description for galaxies?
1128 	if (nType==NebGx || nType==NebAGx || nType==NebRGx || nType==NebIGx || nType==NebQSO || nType==NebPossQSO || nType==NebBLA || nType==NebBLL || nType==NebGxCl)
1129 		return QString();
1130 
1131 	QRegularExpression GlClRx("\\.*(I|II|III|IV|V|VI|VI|VII|VIII|IX|X|XI|XII)\\.*");
1132 	int idx = mTypeString.indexOf(GlClRx);
1133 	if (idx>0)
1134 		m = mTypeString.mid(idx);
1135 	else
1136 		m = mTypeString;
1137 
1138 	static const QStringList glclass = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"};
1139 
1140 	QRegularExpressionMatch GlClMatch=GlClRx.match(m);
1141 	if (GlClMatch.hasMatch()) // Globular Clusters
1142 	{
1143 		switch(glclass.indexOf(GlClMatch.captured(1).trimmed()))
1144 		{
1145 			case 0:
1146 				r = qc_("high concentration of stars toward the center", "Shapley-Sawyer Concentration Class");
1147 				break;
1148 			case 1:
1149 				r = qc_("dense central concentration of stars", "Shapley-Sawyer Concentration Class");
1150 				break;
1151 			case 2:
1152 				r = qc_("strong inner core of stars", "Shapley-Sawyer Concentration Class");
1153 				break;
1154 			case 3:
1155 				r = qc_("intermediate rich concentrations of stars", "Shapley-Sawyer Concentration Class");
1156 				break;
1157 			case 4:
1158 			case 5:
1159 			case 6:
1160 				r = qc_("intermediate concentrations of stars", "Shapley-Sawyer Concentration Class");
1161 				break;
1162 			case 7:
1163 				r = qc_("rather loose concentration of stars towards the center", "Shapley-Sawyer Concentration Class");
1164 				break;
1165 			case 8:
1166 				r = qc_("loose concentration of stars towards the center", "Shapley-Sawyer Concentration Class");
1167 				break;
1168 			case 9:
1169 				r = qc_("loose concentration of stars", "Shapley-Sawyer Concentration Class");
1170 				break;
1171 			case 10:
1172 				r = qc_("very loose concentration of stars towards the center", "Shapley-Sawyer Concentration Class");
1173 				break;
1174 			case 11:
1175 				r = qc_("almost no concentration towards the center", "Shapley-Sawyer Concentration Class");
1176 				break;
1177 			default:
1178 				r = qc_("undocumented concentration class", "Shapley-Sawyer Concentration Class");
1179 				break;
1180 		}
1181 	}
1182 
1183 	QRegularExpression OClRx("\\.*(I|II|III|IV)(\\d)(p|m|r)(n*|N*|u*|U*|e*|E*)\\.*");
1184 	idx = mTypeString.indexOf(OClRx);
1185 	if (idx>0)
1186 		m = mTypeString.mid(idx);
1187 	else
1188 		m = mTypeString;
1189 
1190 	QRegularExpressionMatch OClMatch=OClRx.match(m);
1191 	if (OClMatch.hasMatch()) // Open Clusters
1192 	{
1193 		QStringList rtxt;
1194 		static const QStringList occlass = { "I", "II", "III", "IV"};
1195 		static const QStringList ocrich = { "p", "m", "r"};
1196 		switch(occlass.indexOf(OClMatch.captured(1).trimmed()))
1197 		{
1198 			case 0:
1199 				rtxt << qc_("strong central concentration of stars", "Trumpler's Concentration Class");
1200 				break;
1201 			case 1:
1202 				rtxt << qc_("little central concentration of stars", "Trumpler's Concentration Class");
1203 				break;
1204 			case 2:
1205 				rtxt << qc_("no noticeable concentration of stars", "Trumpler's Concentration Class");
1206 				break;
1207 			case 3:
1208 				rtxt << qc_("a star field condensation", "Trumpler's Concentration Class");
1209 				break;
1210 			default:
1211 				rtxt << qc_("undocumented concentration class", "Trumpler's Concentration Class");
1212 				break;
1213 		}
1214 		switch(OClMatch.captured(2).toInt())
1215 		{
1216 			case 1:
1217 				rtxt << qc_("small brightness range of cluster members", "Trumpler's Brightness Class");
1218 				break;
1219 			case 2:
1220 				rtxt << qc_("medium brightness range of cluster members", "Trumpler's Brightness Class");
1221 				break;
1222 			case 3:
1223 				rtxt << qc_("large brightness range of cluster members", "Trumpler's Brightness Class");
1224 				break;
1225 			default:
1226 				rtxt << qc_("undocumented brightness range of cluster members", "Trumpler's Brightness Class");
1227 				break;
1228 		}
1229 		switch(ocrich.indexOf(OClMatch.captured(3).trimmed()))
1230 		{
1231 			case 0:
1232 				rtxt << qc_("poor cluster with less than 50 stars", "Trumpler's Number of Members Class");
1233 				break;
1234 			case 1:
1235 				rtxt << qc_("moderately rich cluster with 50-100 stars", "Trumpler's Number of Members Class");
1236 				break;
1237 			case 2:
1238 				rtxt << qc_("rich cluster with more than 100 stars", "Trumpler's Number of Members Class");
1239 				break;
1240 			default:
1241 				rtxt << qc_("undocumented number of members class", "Trumpler's Number of Members Class");
1242 				break;
1243 		}
1244 		if (!OClMatch.captured(4).trimmed().isEmpty())
1245 			rtxt << qc_("the cluster lies within nebulosity", "nebulosity factor of open clusters");
1246 
1247 		r = rtxt.join(",<br />");
1248 	}
1249 
1250 	QRegularExpression VdBRx("\\.*(I|II|I-II|II P|P),\\s+(VBR|VB|BR|M|F|VF|:)\\.*");
1251 	idx = mTypeString.indexOf(VdBRx);
1252 	if (idx>0)
1253 		m = mTypeString.mid(idx);
1254 	else
1255 		m = mTypeString;
1256 
1257 	QRegularExpressionMatch VdBMatch=VdBRx.match(m);
1258 	if (VdBMatch.hasMatch()) // Reflection Nebulae
1259 	{
1260 		QStringList rtx;
1261 		static const QStringList rnclass = { "I", "II", "I-II", "II P", "P"};
1262 		static const QStringList rnbrightness = { "VBR", "VB", "BR", "M", "F", "VF", ":"};
1263 		switch(rnbrightness.indexOf(VdBMatch.captured(2).trimmed()))
1264 		{
1265 			case 0:
1266 			case 1:
1267 				rtx << qc_("very bright", "Reflection Nebulae Brightness");
1268 				break;
1269 			case 2:
1270 				rtx << qc_("bright", "Reflection Nebulae Brightness");
1271 				break;
1272 			case 3:
1273 				rtx << qc_("moderate brightness", "Reflection Nebulae Brightness");
1274 				break;
1275 			case 4:
1276 				rtx << qc_("faint", "Reflection Nebulae Brightness");
1277 				break;
1278 			case 5:
1279 				rtx << qc_("very faint", "Reflection Nebulae Brightness");
1280 				break;
1281 			case 6:
1282 				rtx << qc_("uncertain brightness", "Reflection Nebulae Brightness");
1283 				break;
1284 			default:
1285 				rtx << qc_("undocumented brightness of reflection nebulae", "Reflection Nebulae Brightness");
1286 				break;
1287 		}
1288 		switch(rnclass.indexOf(VdBMatch.captured(1).trimmed()))
1289 		{
1290 			case 0:
1291 				rtx << qc_("the illuminating star is embedded in the nebulosity", "Reflection Nebulae Classification");
1292 				break;
1293 			case 1:
1294 				rtx << qc_("star is located outside the illuminated nebulosity", "Reflection Nebulae Classification");
1295 				break;
1296 			case 2:
1297 				rtx << qc_("star is located on the corner of the illuminated nebulosity", "Reflection Nebulae Classification");
1298 				break;
1299 			case 3:
1300 			{
1301 				// TRANSLATORS: peculiar: odd or unusual, cf. peculiar star, peculiar galaxy
1302 				rtx << qc_("star is located outside the illuminated peculiar nebulosity", "Reflection Nebulae Classification");
1303 				break;
1304 			}
1305 			case 4:
1306 			{
1307 				// TRANSLATORS: peculiar: odd or unusual, cf. peculiar star, peculiar galaxy
1308 				rtx << qc_("the illuminated peculiar nebulosity", "Reflection Nebulae Classification");
1309 				break;
1310 			}
1311 			default:
1312 				rtx << qc_("undocumented reflection nebulae", "Reflection Nebulae Classification");
1313 				break;
1314 		}
1315 		r = rtx.join(",<br />");
1316 	}
1317 
1318 
1319 	QRegularExpression HIIRx("\\.*(\\d+),\\s+(\\d+),\\s+(\\d+)\\.*");
1320 	idx = mTypeString.indexOf(HIIRx);
1321 	if (idx>0)
1322 		m = mTypeString.mid(idx);
1323 	else
1324 		m = mTypeString;
1325 
1326 	QRegularExpressionMatch HIIMatch=HIIRx.match(m);
1327 	if (HIIMatch.hasMatch()) // HII regions
1328 	{
1329 		const int form	     = HIIMatch.captured(1).toInt();
1330 		const int structure  = HIIMatch.captured(2).toInt();
1331 		const int brightness = HIIMatch.captured(3).toInt();
1332 		const QStringList formList={
1333 			q_("circular form"),
1334 			q_("elliptical form"),
1335 			q_("irregular form")};
1336 		const QStringList structureList={
1337 			q_("amorphous structure"),
1338 			q_("conventional structure"),
1339 			q_("filamentary structure")};
1340 		const QStringList brightnessList={
1341 			qc_("faintest", "HII region brightness"),
1342 			qc_("moderate brightness", "HII region brightness"),
1343 			qc_("brightest", "HII region brightness")};
1344 
1345 		QStringList morph;
1346 		morph << formList.value(form-1, q_("undocumented form"));
1347 		morph << structureList.value(structure-1, q_("undocumented structure"));
1348 		morph << brightnessList.value(brightness-1, q_("undocumented brightness"));
1349 
1350 		r = morph.join(",<br />");
1351 	}
1352 
1353 	if (nType==NebSNR)
1354 	{
1355 		QString delim = "";
1356 		if (!r.isEmpty())
1357 			delim = ";<br />";
1358 
1359 		if (mTypeString.contains("S") && !mTypeString.contains("S?"))
1360 			r = qc_("remnant shows a shell radio structure", "supernova remnant structure classification") + delim + r;
1361 
1362 		if (mTypeString.contains("F") && !mTypeString.contains("F?"))
1363 			r = qc_("remnant shows a filled center ('plerion') radio structure", "supernova remnant structure classification") + delim + r;
1364 
1365 		if (mTypeString.contains("C") && !mTypeString.contains("C?"))
1366 			r = qc_("remnant shows a composite (or combination) radio structure", "supernova remnant structure classification") + delim + r;
1367 
1368 		if (mTypeString.contains("S?"))
1369 			r = qc_("remnant shows a shell radio structure with some uncertainty", "supernova remnant structure classification") + delim + r;
1370 
1371 		if (mTypeString.contains("F?"))
1372 			r = qc_("remnant shows a filled center ('plerion') radio structure with some uncertainty", "supernova remnant structure classification") + delim + r;
1373 
1374 		if (mTypeString.contains("C?"))
1375 			r = qc_("remnant shows a composite (or combination) radio structure with some uncertainty", "supernova remnant structure classification") + delim + r;
1376 	}
1377 
1378 	return r;
1379 }
1380 
getTypeString(Nebula::NebulaType nType)1381 QString Nebula::getTypeString(Nebula::NebulaType nType)
1382 {
1383 	return typeStringMap.value(nType, q_("undocumented type"));
1384 }
1385 
buildTypeStringMap()1386 void Nebula::buildTypeStringMap()
1387 {
1388 	Nebula::typeStringMap = {
1389 	{ NebGx     , q_("galaxy") },
1390 	{ NebAGx    , q_("active galaxy") },
1391 	{ NebRGx    , q_("radio galaxy") },
1392 	{ NebIGx    , q_("interacting galaxy") },
1393 	{ NebQSO    , q_("quasar") },
1394 	{ NebCl     , q_("star cluster") },
1395 	{ NebOc     , q_("open star cluster") },
1396 	{ NebGc     , q_("globular star cluster") },
1397 	{ NebSA     , q_("stellar association") },
1398 	{ NebSC     , q_("star cloud") },
1399 	{ NebN      , q_("nebula") },
1400 	{ NebPn     , q_("planetary nebula") },
1401 	{ NebDn     , q_("dark nebula") },
1402 	{ NebRn     , q_("reflection nebula") },
1403 	{ NebBn     , q_("bipolar nebula") },
1404 	{ NebEn     , q_("emission nebula") },
1405 	{ NebCn     , q_("cluster associated with nebulosity") },
1406 	{ NebHII    , q_("HII region") },
1407 	{ NebSNR    , q_("supernova remnant") },
1408 	{ NebISM    , q_("interstellar matter") },
1409 	{ NebEMO    , q_("emission object") },
1410 	{ NebBLL    , q_("BL Lac object") },
1411 	{ NebBLA    , q_("blazar") },
1412 	{ NebMolCld , q_("molecular cloud") },
1413 	{ NebYSO    , q_("young stellar object") },
1414 	{ NebPossQSO, q_("possible quasar") },
1415 	{ NebPossPN , q_("possible planetary nebula") },
1416 	{ NebPPN    , q_("protoplanetary nebula") },
1417 	{ NebStar   , q_("star") },
1418 	{ NebSymbioticStar   , q_("symbiotic star") },
1419 	{ NebEmissionLineStar, q_("emission-line star") },
1420 	{ NebSNC    , q_("supernova candidate") },
1421 	{ NebSNRC   , q_("supernova remnant candidate") },
1422 	{ NebGxCl   , q_("cluster of galaxies") },
1423 	{ NebPartOfGx   , q_("part of a galaxy") },
1424 	{ NebRegion , q_("region of the sky") },
1425 	{ NebUnknown, q_("object of unknown nature") }};
1426 }
1427 
getJ2000EquatorialPos(const StelCore * core) const1428 Vec3d Nebula::getJ2000EquatorialPos(const StelCore* core) const
1429 {
1430 	if ((core) && (core->getUseAberration()) && (core->getCurrentPlanet()))
1431 	{
1432 		Vec3d pos=XYZ;
1433 		Q_ASSERT_X(fabs(pos.lengthSquared()-1.0)<0.0001, "Nebula aberration", "vertex length not unity");
1434 		//pos.normalize(); // Yay - not required!
1435 		Vec3d vel=core->getCurrentPlanet()->getHeliocentricEclipticVelocity();
1436 		vel=StelCore::matVsop87ToJ2000*vel*core->getAberrationFactor()*(AU/(86400.0*SPEED_OF_LIGHT));
1437 		pos+=vel;
1438 		pos.normalize();
1439 		return pos;
1440 	}
1441 	else
1442 	{
1443 		return XYZ;
1444 	}
1445 }
1446