1 /*
2  * Stellarium
3  * Copyright (C) 2002 Fabien Chereau
4  * Copyright (C) 2012 Timothy Reaves
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19  */
20 
21 
22 #include "ConstellationMgr.hpp"
23 #include "Constellation.hpp"
24 #include "StarMgr.hpp"
25 #include "StelUtils.hpp"
26 #include "StelApp.hpp"
27 #include "StelTextureMgr.hpp"
28 #include "StelProjector.hpp"
29 #include "StelObjectMgr.hpp"
30 #include "StelLocaleMgr.hpp"
31 #include "StelSkyCultureMgr.hpp"
32 #include "StelModuleMgr.hpp"
33 #include "StelMovementMgr.hpp"
34 #include "StelFileMgr.hpp"
35 #include "StelCore.hpp"
36 #include "StelPainter.hpp"
37 #include "StelSkyDrawer.hpp"
38 #include "SolarSystem.hpp"
39 
40 #include <vector>
41 #include <QDebug>
42 #include <QFile>
43 #include <QSettings>
44 #include <QRegularExpression>
45 #include <QString>
46 #include <QStringList>
47 #include <QDir>
48 
49 using namespace std;
50 
51 // constructor which loads all data from appropriate files
ConstellationMgr(StarMgr * _hip_stars)52 ConstellationMgr::ConstellationMgr(StarMgr *_hip_stars)
53 	: hipStarMgr(_hip_stars),
54 	  isolateSelected(false),
55 	  constellationPickEnabled(false),
56 	  constellationDisplayStyle(ConstellationMgr::constellationsTranslated),
57 	  artFadeDuration(2.),
58 	  artIntensity(0),
59 	  artIntensityMinimumFov(1.0),
60 	  artIntensityMaximumFov(2.0),
61 	  artDisplayed(0),
62 	  boundariesDisplayed(0),
63 	  linesDisplayed(0),
64 	  namesDisplayed(0),
65 	  checkLoadingData(false),
66 	  constellationLineThickness(1),
67 	  constellationBoundariesThickness(1)
68 {
69 	setObjectName("ConstellationMgr");
70 	Q_ASSERT(hipStarMgr);
71 }
72 
~ConstellationMgr()73 ConstellationMgr::~ConstellationMgr()
74 {
75 	for (auto* constellation : constellations)
76 	{
77 		delete constellation;
78 	}
79 
80 	for (auto* segment : allBoundarySegments)
81 	{
82 		delete segment;
83 	}
84 }
85 
init()86 void ConstellationMgr::init()
87 {
88 	QSettings* conf = StelApp::getInstance().getSettings();
89 	Q_ASSERT(conf);
90 
91 	lastLoadedSkyCulture = "dummy";
92 	asterFont.setPixelSize(conf->value("viewing/constellation_font_size", 15).toInt());
93 	setFlagLines(conf->value("viewing/flag_constellation_drawing").toBool());
94 	setFlagLabels(conf->value("viewing/flag_constellation_name").toBool());
95 	setFlagBoundaries(conf->value("viewing/flag_constellation_boundaries",false).toBool());
96 	setArtIntensity(conf->value("viewing/constellation_art_intensity", 0.5f).toFloat());
97 	setArtFadeDuration(conf->value("viewing/constellation_art_fade_duration",2.f).toFloat());
98 	setFlagArt(conf->value("viewing/flag_constellation_art").toBool());
99 	setFlagIsolateSelected(conf->value("viewing/flag_constellation_isolate_selected", false).toBool());
100 	setFlagConstellationPick(conf->value("viewing/flag_constellation_pick", false).toBool());
101 	setConstellationLineThickness(conf->value("viewing/constellation_line_thickness", 1).toInt());
102 	setConstellationBoundariesThickness(conf->value("viewing/constellation_boundaries_thickness", 1).toInt());
103 	// The setting for developers
104 	setFlagCheckLoadingData(conf->value("devel/check_loading_constellation_data","false").toBool());
105 
106 	QString starloreDisplayStyle=conf->value("viewing/constellation_name_style", "translated").toString();
107 	static const QMap<QString, ConstellationDisplayStyle>map={
108 		{ "translated",  constellationsTranslated},
109 		{ "native",      constellationsNative},
110 		{ "abbreviated", constellationsAbbreviated},
111 		{ "english",     constellationsEnglish}};
112 	if (!map.contains(starloreDisplayStyle))
113 	{
114 		qDebug() << "Warning: viewing/constellation_name_style (" << starloreDisplayStyle << ") invalid. Using translated style.";
115 		conf->setValue("viewing/constellation_name_style", "translated");
116 	}
117 	setConstellationDisplayStyle(map.value(starloreDisplayStyle, constellationsTranslated));
118 
119 	// Load colors from config file
120 	QString defaultColor = conf->value("color/default_color").toString();
121 	setLinesColor(Vec3f(conf->value("color/const_lines_color", defaultColor).toString()));
122 	setBoundariesColor(Vec3f(conf->value("color/const_boundary_color", "0.8,0.3,0.3").toString()));
123 	setLabelsColor(Vec3f(conf->value("color/const_names_color", defaultColor).toString()));
124 
125 	StelObjectMgr *objectManager = GETSTELMODULE(StelObjectMgr);
126 	objectManager->registerStelObjectMgr(this);
127 	connect(objectManager, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)),
128 			this, SLOT(selectedObjectChange(StelModule::StelModuleSelectAction)));
129 	StelApp *app = &StelApp::getInstance();
130 	connect(app, SIGNAL(languageChanged()), this, SLOT(updateI18n()));
131 	connect(&app->getSkyCultureMgr(), SIGNAL(currentSkyCultureChanged(QString)), this, SLOT(updateSkyCulture(const QString&)));
132 
133 	QString displayGroup = N_("Display Options");
134 	addAction("actionShow_Constellation_Lines", displayGroup, N_("Constellation lines"), "linesDisplayed", "C");
135 	addAction("actionShow_Constellation_Art", displayGroup, N_("Constellation art"), "artDisplayed", "R");
136 	addAction("actionShow_Constellation_Labels", displayGroup, N_("Constellation labels"), "namesDisplayed", "V");
137 	addAction("actionShow_Constellation_Boundaries", displayGroup, N_("Constellation boundaries"), "boundariesDisplayed", "B");
138 	addAction("actionShow_Constellation_Isolated", displayGroup, N_("Select single constellation"), "isolateSelected"); // no shortcut, sync with GUI
139 	addAction("actionShow_Constellation_Deselect", displayGroup, N_("Remove selection of constellations"), this, "deselectConstellations()", "W");
140 	addAction("actionShow_Constellation_Select", displayGroup, N_("Select all constellations"), this, "selectAllConstellations()", "Alt+W");
141 	// Reload the current sky culture
142 	addAction("actionShow_Starlore_Reload", displayGroup, N_("Reload the sky culture"), this, "reloadSkyCulture()", "Ctrl+Alt+I");
143 }
144 
145 /*************************************************************************
146  Reimplementation of the getCallOrder method
147 *************************************************************************/
getCallOrder(StelModuleActionName actionName) const148 double ConstellationMgr::getCallOrder(StelModuleActionName actionName) const
149 {
150 	if (actionName==StelModule::ActionDraw)
151 		return StelApp::getInstance().getModuleMgr().getModule("GridLinesMgr")->getCallOrder(actionName)+10;
152 	return 0;
153 }
154 
reloadSkyCulture()155 void ConstellationMgr::reloadSkyCulture()
156 {
157 	updateSkyCulture(StelApp::getInstance().getSkyCultureMgr().getCurrentSkyCultureID());
158 }
159 
updateSkyCulture(const QString & skyCultureDir)160 void ConstellationMgr::updateSkyCulture(const QString& skyCultureDir)
161 {
162 	// Check if the sky culture changed since last load, if not don't load anything
163 	if (lastLoadedSkyCulture == skyCultureDir)
164 		return;
165 
166 	// Find constellation art.  If this doesn't exist, warn, but continue using ""
167 	// the loadLinesAndArt function knows how to handle this (just loads lines).
168 	QString conArtFile = StelFileMgr::findFile("skycultures/"+skyCultureDir+"/constellationsart.fab");
169 	if (conArtFile.isEmpty())
170 	{
171 		qDebug() << "No constellationsart.fab file found for sky culture dir" << QDir::toNativeSeparators(skyCultureDir);
172 	}
173 
174 	// first of all, remove constellations from the list of selected objects in StelObjectMgr, since we are going to delete them
175 	deselectConstellations();
176 
177 	QString fic = StelFileMgr::findFile("skycultures/"+skyCultureDir+"/constellationship.fab");
178 	if (fic.isEmpty())
179 		qWarning() << "ERROR loading constellation lines and art from file: " << fic;
180 	else
181 		loadLinesAndArt(fic, conArtFile, skyCultureDir);
182 
183 	// load constellation names
184 	fic = StelFileMgr::findFile("skycultures/" + skyCultureDir + "/constellation_names.eng.fab");
185 	if (fic.isEmpty())
186 		qWarning() << "ERROR loading constellation names from file: " << fic;
187 	else
188 		loadNames(fic);
189 
190 	// load seasonal rules
191 	loadSeasonalRules(StelFileMgr::findFile("skycultures/" + skyCultureDir + "/seasonal_rules.fab"));
192 
193 	// Translate constellation names for the new sky culture
194 	updateI18n();
195 
196 	// load constellation boundaries
197 	StelApp *app = &StelApp::getInstance();
198 	int idx = app->getSkyCultureMgr().getCurrentSkyCultureBoundariesIdx();
199 	if (idx>=0)
200 	{
201 		// OK, the current sky culture has boundaries!
202 		if (idx==1)
203 		{
204 			// boundaries = own
205 			fic = StelFileMgr::findFile("skycultures/" + skyCultureDir + "/constellation_boundaries.dat");
206 			if (fic.isEmpty()) // Check old file name (backward compatibility)
207 				fic = StelFileMgr::findFile("skycultures/" + skyCultureDir + "/constellations_boundaries.dat");
208 		}
209 		else
210 		{
211 			// boundaries = generic
212 			fic = StelFileMgr::findFile("data/constellation_boundaries.dat");
213 		}
214 
215 		if (fic.isEmpty())
216 			qWarning() << "ERROR loading constellation boundaries file: " << fic;
217 		else
218 			loadBoundaries(fic);
219 	}
220 
221 	lastLoadedSkyCulture = skyCultureDir;
222 
223 	if (getFlagCheckLoadingData())
224 	{
225 		int i = 1;
226 		for (auto* constellation : constellations)
227 		{
228 			qWarning() << "[Constellation] #" << i << " abbr:" << constellation->abbreviation << " name:" << constellation->getEnglishName() << " segments:" << constellation->numberOfSegments;
229 			i++;
230 		}
231 	}
232 }
233 
selectedObjectChange(StelModule::StelModuleSelectAction action)234 void ConstellationMgr::selectedObjectChange(StelModule::StelModuleSelectAction action)
235 {
236 	StelObjectMgr* omgr = GETSTELMODULE(StelObjectMgr);
237 	Q_ASSERT(omgr);
238 	const QList<StelObjectP> newSelected = omgr->getSelectedObject();
239 	if (newSelected.empty())
240 	{
241 		// Even if do not have anything selected, KEEP constellation selection intact
242 		// (allows viewing constellations without distraction from star pointer animation)
243 		// setSelected(Q_NULLPTR);
244 		return;
245 	}
246 
247 	const QList<StelObjectP> newSelectedConst = omgr->getSelectedObject("Constellation");
248 	if (!newSelectedConst.empty())
249 	{
250 		// If removing this selection
251 		if(action == StelModule::RemoveFromSelection)
252 		{
253 			unsetSelectedConst(static_cast<Constellation *>(newSelectedConst[0].data()));
254 		}
255 		else
256 		{
257 			// Add constellation to selected list (do not select a star, just the constellation)
258 			setSelectedConst(static_cast<Constellation *>(newSelectedConst[0].data()));
259 		}
260 	}
261 	else
262 	{
263 		QList<StelObjectP> newSelectedObject;
264 		if (StelApp::getInstance().getSkyCultureMgr().getCurrentSkyCultureBoundariesIdx()==0) // generic IAU boundaries
265 			newSelectedObject = omgr->getSelectedObject();
266 		else
267 			newSelectedObject = omgr->getSelectedObject("Star");
268 
269 		if (!newSelectedObject.empty())
270 		{
271 			setSelected(newSelectedObject[0].data());
272 		}
273 		else
274 		{
275 			setSelected(Q_NULLPTR);
276 		}
277 	}
278 }
279 
deselectConstellations(void)280 void ConstellationMgr::deselectConstellations(void)
281 {
282 	StelObjectMgr* omgr = GETSTELMODULE(StelObjectMgr);
283 	Q_ASSERT(omgr);
284 	if (getFlagIsolateSelected())
285 	{
286 		// The list of selected constellations is empty, but...
287 		if (selected.size()==0)
288 		{
289 			// ...let's unselect all constellations for guarantee
290 			for (auto* constellation : constellations)
291 			{
292 				constellation->setFlagLines(false);
293 				constellation->setFlagLabels(false);
294 				constellation->setFlagArt(false);
295 				constellation->setFlagBoundaries(false);
296 			}
297 		}
298 
299 		// If any constellation is selected at the moment, then let's do not touch to it!
300 		if (omgr->getWasSelected() && selected.size()>0)
301 			selected.pop_back();
302 
303 		// Let's hide all previously selected constellations
304 		for (auto* constellation : selected)
305 		{
306 			constellation->setFlagLines(false);
307 			constellation->setFlagLabels(false);
308 			constellation->setFlagArt(false);
309 			constellation->setFlagBoundaries(false);
310 		}
311 	}
312 	else
313 	{
314 		const QList<StelObjectP> newSelectedConst = omgr->getSelectedObject("Constellation");
315 		if (!newSelectedConst.empty())
316 			omgr->unSelect();
317 	}
318 	selected.clear();
319 }
320 
selectAllConstellations()321 void ConstellationMgr::selectAllConstellations()
322 {
323 	for (auto* constellation : constellations)
324 	{
325 		setSelectedConst(constellation);
326 	}
327 }
328 
selectConstellation(const QString & englishName)329 void ConstellationMgr::selectConstellation(const QString &englishName)
330 {
331 	if (!getFlagIsolateSelected())
332 		setFlagIsolateSelected(true); // Enable isolated selection
333 
334 	bool found = false;
335 	for (auto* constellation : constellations)
336 	{
337 		if (constellation->getEnglishName().toLower()==englishName.toLower())
338 		{
339 			setSelectedConst(constellation);
340 			found = true;
341 		}
342 	}
343 	if (!found)
344 		qDebug() << "The constellation" << englishName << "is not found";
345 }
346 
selectConstellationByObjectName(const QString & englishName)347 void ConstellationMgr::selectConstellationByObjectName(const QString &englishName)
348 {
349 	if (!getFlagIsolateSelected())
350 		setFlagIsolateSelected(true); // Enable isolated selection
351 
352 	if (StelApp::getInstance().getSkyCultureMgr().getCurrentSkyCultureBoundariesIdx()==0) // generic IAU boundaries
353 		setSelectedConst(isObjectIn(GETSTELMODULE(StelObjectMgr)->searchByName(englishName).data()));
354 	else
355 		setSelectedConst(isStarIn(GETSTELMODULE(StelObjectMgr)->searchByName(englishName).data()));
356 }
357 
deselectConstellation(const QString & englishName)358 void ConstellationMgr::deselectConstellation(const QString &englishName)
359 {
360 	if (!getFlagIsolateSelected())
361 		setFlagIsolateSelected(true); // Enable isolated selection
362 
363 	bool found = false;
364 	for (auto* constellation : constellations)
365 	{
366 		if (constellation->getEnglishName().toLower()==englishName.toLower())
367 		{
368 			unsetSelectedConst(constellation);
369 			found = true;
370 		}
371 	}
372 
373 	if (selected.size()==0 && found)
374 	{
375 		// Let's remove the selection for all constellations if the list of selected constellations is empty
376 		for (auto* constellation : constellations)
377 		{
378 			constellation->setFlagLines(false);
379 			constellation->setFlagLabels(false);
380 			constellation->setFlagArt(false);
381 			constellation->setFlagBoundaries(false);
382 		}
383 	}
384 
385 	if (!found)
386 		qDebug() << "The constellation" << englishName << "is not found";
387 }
388 
setLinesColor(const Vec3f & color)389 void ConstellationMgr::setLinesColor(const Vec3f& color)
390 {
391 	if (color != Constellation::lineColor)
392 	{
393 		Constellation::lineColor = color;
394 		emit linesColorChanged(color);
395 	}
396 }
397 
getLinesColor() const398 Vec3f ConstellationMgr::getLinesColor() const
399 {
400 	return Constellation::lineColor;
401 }
402 
setBoundariesColor(const Vec3f & color)403 void ConstellationMgr::setBoundariesColor(const Vec3f& color)
404 {
405 	if (Constellation::boundaryColor != color)
406 	{
407 		Constellation::boundaryColor = color;
408 		emit boundariesColorChanged(color);
409 	}
410 }
411 
getBoundariesColor() const412 Vec3f ConstellationMgr::getBoundariesColor() const
413 {
414 	return Constellation::boundaryColor;
415 }
416 
setLabelsColor(const Vec3f & color)417 void ConstellationMgr::setLabelsColor(const Vec3f& color)
418 {
419 	if (Constellation::labelColor != color)
420 	{
421 		Constellation::labelColor = color;
422 		emit namesColorChanged(color);
423 	}
424 }
425 
getLabelsColor() const426 Vec3f ConstellationMgr::getLabelsColor() const
427 {
428 	return Constellation::labelColor;
429 }
430 
setFontSize(const float newFontSize)431 void ConstellationMgr::setFontSize(const float newFontSize)
432 {
433 	if (asterFont.pixelSize() - newFontSize != 0.0f)
434 	{
435 		asterFont.setPixelSize(static_cast<int>(newFontSize));
436 		emit fontSizeChanged(newFontSize);
437 	}
438 }
439 
getFontSize() const440 float ConstellationMgr::getFontSize() const
441 {
442 	return asterFont.pixelSize();
443 }
444 
setConstellationDisplayStyle(ConstellationDisplayStyle style)445 void ConstellationMgr::setConstellationDisplayStyle(ConstellationDisplayStyle style)
446 {
447 	constellationDisplayStyle=style;
448 	emit constellationsDisplayStyleChanged(constellationDisplayStyle);
449 }
450 
getConstellationDisplayStyleString(ConstellationDisplayStyle style)451 QString ConstellationMgr::getConstellationDisplayStyleString(ConstellationDisplayStyle style)
452 {
453 	return (style == constellationsAbbreviated ? "abbreviated" : (style == constellationsNative ? "native" : "translated"));
454 }
455 
getConstellationDisplayStyle()456 ConstellationMgr::ConstellationDisplayStyle ConstellationMgr::getConstellationDisplayStyle()
457 {
458 	return constellationDisplayStyle;
459 }
460 
setConstellationLineThickness(const int thickness)461 void ConstellationMgr::setConstellationLineThickness(const int thickness)
462 {
463 	if(thickness!=constellationLineThickness)
464 	{
465 		constellationLineThickness = thickness;
466 		if (constellationLineThickness<=0) // The line can not be negative or zero thickness
467 			constellationLineThickness = 1;
468 
469 		emit constellationLineThicknessChanged(thickness);
470 	}
471 }
472 
setConstellationBoundariesThickness(const int thickness)473 void ConstellationMgr::setConstellationBoundariesThickness(const int thickness)
474 {
475 	if(thickness!=constellationBoundariesThickness)
476 	{
477 		constellationBoundariesThickness = thickness;
478 		if (constellationBoundariesThickness<=0) // The line can not be negative or zero thickness
479 			constellationBoundariesThickness = 1;
480 
481 		emit constellationBoundariesThicknessChanged(thickness);
482 	}
483 }
484 
loadLinesAndArt(const QString & fileName,const QString & artfileName,const QString & cultureName)485 void ConstellationMgr::loadLinesAndArt(const QString &fileName, const QString &artfileName, const QString& cultureName)
486 {
487 	QFile in(fileName);
488 	if (!in.open(QIODevice::ReadOnly | QIODevice::Text))
489 	{
490 		qWarning() << "Can't open constellation data file" << QDir::toNativeSeparators(fileName)  << "for culture" << cultureName;
491 		Q_ASSERT(0);
492 	}
493 
494 	int totalRecords=0;
495 	QString record;
496 	QRegularExpression commentRx("^(\\s*#.*|\\s*)$"); // pure comment lines or empty lines
497 	while (!in.atEnd())
498 	{
499 		record = QString::fromUtf8(in.readLine());
500 		if (!commentRx.match(record).hasMatch())
501 			totalRecords++;
502 	}
503 	in.seek(0);
504 
505 	// delete existing data, if any
506 	for (auto* constellation : constellations)
507 		delete constellation;
508 
509 	constellations.clear();
510 	Constellation *cons = Q_NULLPTR;
511 
512 	// read the file of line patterns, adding a record per non-comment line
513 	int currentLineNumber = 0;	// line in file
514 	int readOk = 0;			// count of records processed OK
515 	while (!in.atEnd())
516 	{
517 		record = QString::fromUtf8(in.readLine());
518 		currentLineNumber++;
519 		if (commentRx.match(record).hasMatch())
520 			continue;
521 
522 		cons = new Constellation;
523 		if(cons->read(record, hipStarMgr))
524 		{
525 			cons->artOpacity = artIntensity;
526 			cons->artFader.setDuration(static_cast<int>(artFadeDuration * 1000.f));
527 			cons->setFlagArt(artDisplayed);
528 			cons->setFlagBoundaries(boundariesDisplayed);
529 			cons->setFlagLines(linesDisplayed);
530 			cons->setFlagLabels(namesDisplayed);
531 			constellations.push_back(cons);
532 			++readOk;
533 		}
534 		else
535 		{
536 			qWarning() << "ERROR reading constellation lines record at line " << currentLineNumber << "for culture" << cultureName;
537 			delete cons;
538 		}
539 	}
540 	in.close();
541 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "constellation records successfully for culture" << cultureName;
542 
543 	// Set current states
544 	setFlagArt(artDisplayed);
545 	setFlagLines(linesDisplayed);
546 	setFlagLabels(namesDisplayed);
547 	setFlagBoundaries(boundariesDisplayed);
548 
549 	// It's possible to have no art - just constellations
550 	if (artfileName.isNull() || artfileName.isEmpty())
551 		return;
552 	QFile fic(artfileName);
553 	if (!fic.open(QIODevice::ReadOnly | QIODevice::Text))
554 	{
555 		qWarning() << "Can't open constellation art file" << QDir::toNativeSeparators(fileName)  << "for culture" << cultureName;
556 		return;
557 	}
558 
559 	totalRecords=0;
560 	while (!fic.atEnd())
561 	{
562 		record = QString::fromUtf8(fic.readLine());
563 		if (!commentRx.match(record).hasMatch())
564 			totalRecords++;
565 	}
566 	fic.seek(0);
567 
568 	// Read the constellation art file with the following format :
569 	// ShortName texture_file x1 y1 hp1 x2 y2 hp2
570 	// Where :
571 	// shortname is the international short name (i.e "Lep" for Lepus)
572 	// texture_file is the graphic file of the art texture
573 	// x1 y1 are the x and y texture coordinates in pixels of the star of hipparcos number hp1
574 	// x2 y2 are the x and y texture coordinates in pixels of the star of hipparcos number hp2
575 	// The coordinate are taken with (0,0) at the top left corner of the image file
576 	QString shortname;
577 	QString texfile;
578 	unsigned int x1, y1, x2, y2, x3, y3, hp1, hp2, hp3;
579 
580 	currentLineNumber = 0;	// line in file
581 	readOk = 0;		// count of records processed OK
582 
583 	while (!fic.atEnd())
584 	{
585 		++currentLineNumber;
586 		record = QString::fromUtf8(fic.readLine());
587 		if (commentRx.match(record).hasMatch())
588 			continue;
589 
590 		// prevent leading zeros on numbers from being interpreted as octal numbers
591 		record.replace(" 0", " ");
592 		QTextStream rStr(&record);
593 		rStr >> shortname >> texfile >> x1 >> y1 >> hp1 >> x2 >> y2 >> hp2 >> x3 >> y3 >> hp3;
594 		if (rStr.status()!=QTextStream::Ok)
595 		{
596 			qWarning() << "ERROR parsing constellation art record at line" << currentLineNumber << "of art file for culture" << cultureName;
597 			continue;
598 		}
599 
600 		// Draw loading bar
601 // 		lb.SetMessage(q_("Loading Constellation Art: %1/%2").arg(currentLineNumber).arg(totalRecords));
602 // 		lb.Draw((float)(currentLineNumber)/totalRecords);
603 
604 		cons = Q_NULLPTR;
605 		cons = findFromAbbreviation(shortname);
606 		if (!cons)
607 		{
608 			qWarning() << "ERROR in constellation art file at line" << currentLineNumber << "for culture" << cultureName
609 					   << "constellation" << shortname << "unknown";
610 		}
611 		else
612 		{
613 			QString texturePath = StelFileMgr::findFile("skycultures/"+cultureName+"/"+texfile);
614 			if (texturePath.isEmpty())
615 			{
616 				qWarning() << "ERROR: could not find texture, " << QDir::toNativeSeparators(texfile);
617 			}
618 
619 			cons->artTexture = StelApp::getInstance().getTextureManager().createTextureThread(texturePath, StelTexture::StelTextureParams(true));
620 
621 			int texSizeX = 0, texSizeY = 0;
622 			if (cons->artTexture==Q_NULLPTR || !cons->artTexture->getDimensions(texSizeX, texSizeY))
623 			{
624 				qWarning() << "Texture dimension not available";
625 			}
626 
627 			StelCore* core = StelApp::getInstance().getCore();
628 			const Vec3d s1 = hipStarMgr->searchHP(static_cast<int>(hp1))->getJ2000EquatorialPos(core);
629 			const Vec3d s2 = hipStarMgr->searchHP(static_cast<int>(hp2))->getJ2000EquatorialPos(core);
630 			const Vec3d s3 = hipStarMgr->searchHP(static_cast<int>(hp3))->getJ2000EquatorialPos(core);
631 
632 			// To transform from texture coordinate to 2d coordinate we need to find X with XA = B
633 			// A formed of 4 points in texture coordinate, B formed with 4 points in 3d coordinate space
634 			// We need 3 stars and the 4th point is deduced from the others to get a normal base
635 			// X = B inv(A)
636 			Vec3d s4 = s1 + ((s2 - s1) ^ (s3 - s1));
637 			Mat4d B(s1[0], s1[1], s1[2], 1, s2[0], s2[1], s2[2], 1, s3[0], s3[1], s3[2], 1, s4[0], s4[1], s4[2], 1);
638 			Mat4d A(x1, texSizeY - static_cast<int>(y1), 0., 1., x2, texSizeY - static_cast<int>(y2), 0., 1., x3, texSizeY - static_cast<int>(y3), 0., 1., x1, texSizeY - static_cast<int>(y1), texSizeX, 1.);
639 			Mat4d X = B * A.inverse();
640 
641 			// Tessellate on the plane assuming a tangential projection for the image
642 			static const int nbPoints=5;
643 			QVector<Vec2f> texCoords;
644 			texCoords.reserve(nbPoints*nbPoints*6);
645 			for (int j=0;j<nbPoints;++j)
646 			{
647 				for (int i=0;i<nbPoints;++i)
648 				{
649 					texCoords << Vec2f((static_cast<float>(i))/nbPoints, (static_cast<float>(j))/nbPoints);
650 					texCoords << Vec2f((static_cast<float>(i)+1.f)/nbPoints, (static_cast<float>(j))/nbPoints);
651 					texCoords << Vec2f((static_cast<float>(i))/nbPoints, (static_cast<float>(j)+1.f)/nbPoints);
652 					texCoords << Vec2f((static_cast<float>(i)+1.f)/nbPoints, (static_cast<float>(j))/nbPoints);
653 					texCoords << Vec2f((static_cast<float>(i)+1.f)/nbPoints, (static_cast<float>(j)+1.f)/nbPoints);
654 					texCoords << Vec2f((static_cast<float>(i))/nbPoints, (static_cast<float>(j)+1.f)/nbPoints);
655 				}
656 			}
657 
658 			QVector<Vec3d> contour;
659 			contour.reserve(texCoords.size());
660 			for (const auto& v : texCoords)
661 			{
662 				Vec3d vertex = X * Vec3d(static_cast<double>(v[0]) * texSizeX, static_cast<double>(v[1]) * texSizeY, 0.);
663 				// Originally the projected texture plane remained as tangential plane.
664 				// The vertices should however be reduced to the sphere for correct aberration:
665 				vertex.normalize();
666 				contour << vertex;
667 			}
668 
669 			cons->artPolygon.vertex=contour;
670 			cons->artPolygon.texCoords=texCoords;
671 			cons->artPolygon.primitiveType=StelVertexArray::Triangles;
672 
673 			Vec3d tmp(X * Vec3d(0.5*texSizeX, 0.5*texSizeY, 0.));
674 			tmp.normalize();
675 			Vec3d tmp2(X * Vec3d(0., 0., 0.));
676 			tmp2.normalize();
677 			cons->boundingCap.n=tmp;
678 			cons->boundingCap.d=tmp*tmp2;
679 			++readOk;
680 		}
681 	}
682 
683 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "constellation art records successfully for culture" << cultureName;
684 	fic.close();
685 }
686 
draw(StelCore * core)687 void ConstellationMgr::draw(StelCore* core)
688 {
689 	const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000);
690 	StelPainter sPainter(prj);
691 	sPainter.setFont(asterFont);
692 	drawLines(sPainter, core);
693 	Vec3d vel(0.);
694 	if (core->getUseAberration())
695 	{
696 		vel=core->getCurrentPlanet()->getHeliocentricEclipticVelocity();
697 		vel=StelCore::matVsop87ToJ2000*vel;
698 		vel*=core->getAberrationFactor() * (AU/(86400.0*SPEED_OF_LIGHT));
699 	}
700 	drawNames(sPainter, vel);
701 	drawArt(sPainter, vel);
702 	drawBoundaries(sPainter, vel);
703 }
704 
705 // Draw constellations art textures
drawArt(StelPainter & sPainter,const Vec3d & obsVelocity) const706 void ConstellationMgr::drawArt(StelPainter& sPainter, const Vec3d &obsVelocity) const
707 {
708 	sPainter.setBlending(true, GL_ONE, GL_ONE);
709 	sPainter.setCullFace(true);
710 
711 	SphericalRegionP region = sPainter.getProjector()->getViewportConvexPolygon();
712 	for (auto* constellation : constellations)
713 	{
714 		constellation->drawArtOptim(sPainter, *region, obsVelocity);
715 	}
716 
717 	sPainter.setCullFace(false);
718 }
719 
720 // Draw constellations lines
drawLines(StelPainter & sPainter,const StelCore * core) const721 void ConstellationMgr::drawLines(StelPainter& sPainter, const StelCore* core) const
722 {
723 	const float ppx = static_cast<float>(sPainter.getProjector()->getDevicePixelsPerPixel());
724 	sPainter.setBlending(true);
725 	if (constellationLineThickness>1 || ppx>1.f)
726 		sPainter.setLineWidth(constellationLineThickness*ppx); // set line thickness
727 	sPainter.setLineSmooth(true);
728 
729 	const SphericalCap& viewportHalfspace = sPainter.getProjector()->getBoundingCap();
730 	for (auto* constellation : constellations)
731 	{
732 		constellation->drawOptim(sPainter, core, viewportHalfspace);
733 	}
734 	if (constellationLineThickness>1 || ppx>1.f)
735 		sPainter.setLineWidth(1); // restore line thickness
736 	sPainter.setLineSmooth(false);
737 }
738 
739 // Draw the names of all the constellations
drawNames(StelPainter & sPainter,const Vec3d & obsVelocity) const740 void ConstellationMgr::drawNames(StelPainter& sPainter, const Vec3d &obsVelocity) const
741 {
742 	sPainter.setBlending(true);
743 	for (auto* constellation : constellations)
744 	{
745 		Vec3d XYZname=constellation->XYZname;
746 		XYZname.normalize();
747 		XYZname+=obsVelocity;
748 		XYZname.normalize();
749 
750 		// Check if in the field of view
751 		if (sPainter.getProjector()->projectCheck(XYZname, constellation->XYname))
752 			constellation->drawName(sPainter, constellationDisplayStyle);
753 	}
754 }
755 
isStarIn(const StelObject * s) const756 Constellation *ConstellationMgr::isStarIn(const StelObject* s) const
757 {
758 	for (auto* constellation : constellations)
759 	{
760 		// Check if the star is in one of the constellation
761 		if (constellation->isStarIn(s))
762 		{
763 			return constellation;
764 		}
765 	}
766 	return Q_NULLPTR;
767 }
768 
findFromAbbreviation(const QString & abbreviation) const769 Constellation* ConstellationMgr::findFromAbbreviation(const QString& abbreviation) const
770 {
771 	// search in uppercase only
772 	//QString tname = abbreviation.toUpper();
773 
774 	for (auto* constellation : constellations)
775 	{
776 		//if (constellation->abbreviation.toUpper() == tname)
777 		if (constellation->abbreviation.compare(abbreviation, Qt::CaseInsensitive) == 0)
778 		{
779 			//if (constellation->abbreviation != abbreviation)
780 			//	qDebug() << "ConstellationMgr::findFromAbbreviation: not a perfect match, but sufficient:" << constellation->abbreviation << "vs." << abbreviation;
781 			return constellation;
782 		}
783 		//else qDebug() << "Comparison mismatch: " << abbreviation << "vs." << constellation->abbreviation;
784 	}
785 	return Q_NULLPTR;
786 }
787 
788 // Can't find constellation from a position because it's not well localized
searchAround(const Vec3d &,double,const StelCore *) const789 QList<StelObjectP> ConstellationMgr::searchAround(const Vec3d&, double, const StelCore*) const
790 {
791 	return QList<StelObjectP>();
792 }
793 
loadNames(const QString & namesFile)794 void ConstellationMgr::loadNames(const QString& namesFile)
795 {
796 	// Constellation not loaded yet
797 	if (constellations.empty()) return;
798 
799 	// clear previous names
800 	for (auto* constellation : constellations)
801 	{
802 		constellation->englishName.clear();
803 	}
804 
805 	// Open file
806 	QFile commonNameFile(namesFile);
807 	if (!commonNameFile.open(QIODevice::ReadOnly | QIODevice::Text))
808 	{
809 		qDebug() << "Cannot open file" << QDir::toNativeSeparators(namesFile);
810 		return;
811 	}
812 
813 	constellationsEnglishNames.clear();
814 
815 	// Now parse the file
816 	// lines to ignore which start with a # or are empty
817 	QRegularExpression commentRx("^(\\s*#.*|\\s*)$");
818 
819 	// lines which look like records - we use the RE to extract the fields
820 	// which will be available in recRx.capturedTexts()
821 	// abbreviation is allowed to start with a dot to mark as "hidden".
822 	QRegularExpression recRx("^\\s*(\\.?\\w+)\\s+\"(.*)\"\\s+_[(]\"(.*)\"[)]\\s*([\\,\\d\\s]*)\\n");
823 	QRegularExpression ctxRx("(.*)\",\\s*\"(.*)");
824 
825 	// keep track of how many records we processed.
826 	int totalRecords=0;
827 	int readOk=0;
828 	int lineNumber=0;
829 	while (!commonNameFile.atEnd())
830 	{
831 		QString record = QString::fromUtf8(commonNameFile.readLine());
832 		lineNumber++;
833 
834 		// Skip comments
835 		if (commentRx.match(record).hasMatch())
836 			continue;
837 
838 		totalRecords++;
839 
840 		QRegularExpressionMatch recMatch=recRx.match(record);
841 		if (!recMatch.hasMatch())
842 		{
843 			qWarning() << "ERROR - cannot parse record at line" << lineNumber << "in constellation names file" << QDir::toNativeSeparators(namesFile) << ":" << record;
844 		}
845 		else
846 		{
847 			QString shortName = recMatch.captured(1);
848 			Constellation *aster = findFromAbbreviation(shortName);
849 			// If the constellation exists, set the English name
850 			if (aster != Q_NULLPTR)
851 			{
852 				aster->nativeName = recMatch.captured(2);
853 				QString ctxt = recMatch.captured(3);
854 				QRegularExpressionMatch ctxMatch=ctxRx.match(ctxt);
855 				if (ctxMatch.hasMatch())
856 				{
857 					aster->englishName = ctxMatch.captured(1);
858 					aster->context = ctxMatch.captured(2);
859 				}
860 				else
861 				{
862 					aster->englishName = ctxt;
863 					aster->context = "";
864 				}
865 				readOk++;
866 				// Some skycultures already have empty nativeNames. Fill those.
867 				if (aster->nativeName.isEmpty())
868 					aster->nativeName=aster->englishName;
869 
870 				constellationsEnglishNames << aster->englishName;
871 			}
872 			else
873 			{
874 				qWarning() << "WARNING - constellation abbreviation" << shortName << "not found when loading constellation names";
875 			}
876 		}
877 	}
878 	commonNameFile.close();
879 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "constellation names";
880 }
881 
getConstellationsEnglishNames()882 QStringList ConstellationMgr::getConstellationsEnglishNames()
883 {
884 	return  constellationsEnglishNames;
885 }
886 
loadSeasonalRules(const QString & rulesFile)887 void ConstellationMgr::loadSeasonalRules(const QString& rulesFile)
888 {
889 	// Constellation not loaded yet
890 	if (constellations.empty()) return;
891 
892 	bool flag = true;
893 	if (rulesFile.isEmpty())
894 		flag = false;
895 
896 	// clear previous rules
897 	for (auto* constellation : constellations)
898 	{
899 		constellation->beginSeason = 1;
900 		constellation->endSeason = 12;
901 		constellation->seasonalRuleEnabled = flag;
902 	}
903 
904 	// Current starlore didn't support the seasonal rules
905 	if (!flag)
906 		return;
907 
908 	// Open file
909 	QFile seasonalRulesFile(rulesFile);
910 	if (!seasonalRulesFile.open(QIODevice::ReadOnly | QIODevice::Text))
911 	{
912 		qDebug() << "Cannot open file" << QDir::toNativeSeparators(rulesFile);
913 		return;
914 	}
915 
916 	// Now parse the file
917 	// lines to ignore which start with a # or are empty
918 	QRegularExpression commentRx("^(\\s*#.*|\\s*)$");
919 
920 	// lines which look like records - we use the RE to extract the fields
921 	// which will be available in recRx.capturedTexts()
922 	QRegularExpression recRx("^\\s*(\\w+)\\s+(\\w+)\\s+(\\w+)\\n");
923 
924 	// Some more variables to use in the parsing
925 	Constellation *aster;
926 	QString record, shortName;
927 
928 	// keep track of how many records we processed.
929 	int totalRecords=0;
930 	int readOk=0;
931 	int lineNumber=0;
932 	while (!seasonalRulesFile.atEnd())
933 	{
934 		record = QString::fromUtf8(seasonalRulesFile.readLine());
935 		lineNumber++;
936 
937 		// Skip comments
938 		if (commentRx.match(record).hasMatch())
939 			continue;
940 
941 		totalRecords++;
942 
943 		QRegularExpressionMatch recMatch=recRx.match(record);
944 		if (!recMatch.hasMatch())
945 		{
946 			qWarning() << "ERROR - cannot parse record at line" << lineNumber << "in seasonal rules file" << QDir::toNativeSeparators(rulesFile);
947 		}
948 		else
949 		{
950 			shortName = recMatch.captured(1);
951 			aster = findFromAbbreviation(shortName);
952 			// If the constellation exists, set the English name
953 			if (aster != Q_NULLPTR)
954 			{
955 				aster->beginSeason = recMatch.captured(2).toInt();
956 				aster->endSeason = recMatch.captured(3).toInt();
957 				readOk++;
958 			}
959 			else
960 			{
961 				qWarning() << "WARNING - constellation abbreviation" << shortName << "not found when loading seasonal rules for constellations";
962 			}
963 		}
964 	}
965 	seasonalRulesFile.close();
966 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "seasonal rules";
967 }
968 
updateI18n()969 void ConstellationMgr::updateI18n()
970 {
971 	const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyTranslator();
972 	for (auto* constellation : constellations)
973 	{
974 		constellation->nameI18 = trans.qtranslate(constellation->englishName, constellation->context);
975 	}
976 }
977 
978 // update faders
update(double deltaTime)979 void ConstellationMgr::update(double deltaTime)
980 {
981 	//calculate FOV fade value, linear fade between artIntensityMaximumFov and artIntensityMinimumFov
982 	double fov = StelApp::getInstance().getCore()->getMovementMgr()->getCurrentFov();
983 	Constellation::artIntensityFovScale = static_cast<float>(qBound(0.0,(fov - artIntensityMinimumFov) / (artIntensityMaximumFov - artIntensityMinimumFov),1.0));
984 
985 	const int delta = static_cast<int>(deltaTime*1000);
986 	for (auto* constellation : constellations)
987 	{
988 		constellation->update(delta);
989 	}
990 }
991 
setArtIntensity(const float intensity)992 void ConstellationMgr::setArtIntensity(const float intensity)
993 {
994 	if ((artIntensity - intensity) != 0.0f)
995 	{
996 		artIntensity = intensity;
997 
998 		for (auto* constellation : constellations)
999 		{
1000 			constellation->artOpacity = artIntensity;
1001 		}
1002 
1003 		emit artIntensityChanged(static_cast<double>(intensity));
1004 	}
1005 }
1006 
getArtIntensity() const1007 float ConstellationMgr::getArtIntensity() const
1008 {
1009 	return artIntensity;
1010 }
1011 
setArtIntensityMinimumFov(const double fov)1012 void ConstellationMgr::setArtIntensityMinimumFov(const double fov)
1013 {
1014 	artIntensityMinimumFov = fov;
1015 }
1016 
getArtIntensityMinimumFov() const1017 double ConstellationMgr::getArtIntensityMinimumFov() const
1018 {
1019 	return artIntensityMinimumFov;
1020 }
1021 
setArtIntensityMaximumFov(const double fov)1022 void ConstellationMgr::setArtIntensityMaximumFov(const double fov)
1023 {
1024 	artIntensityMaximumFov = fov;
1025 }
1026 
getArtIntensityMaximumFov() const1027 double ConstellationMgr::getArtIntensityMaximumFov() const
1028 {
1029 	return artIntensityMaximumFov;
1030 }
1031 
setArtFadeDuration(const float duration)1032 void ConstellationMgr::setArtFadeDuration(const float duration)
1033 {
1034     if (!qFuzzyCompare(artFadeDuration, duration))
1035 	{
1036 		artFadeDuration = duration;
1037 
1038 		for (auto* constellation : constellations)
1039 		{
1040 			constellation->artFader.setDuration(static_cast<int>(duration * 1000.f));
1041 		}
1042 		emit artFadeDurationChanged(duration);
1043 	}
1044 }
1045 
getArtFadeDuration() const1046 float ConstellationMgr::getArtFadeDuration() const
1047 {
1048 	return artFadeDuration;
1049 }
1050 
setFlagLines(const bool displayed)1051 void ConstellationMgr::setFlagLines(const bool displayed)
1052 {
1053 	if(linesDisplayed != displayed)
1054 	{
1055 		linesDisplayed = displayed;
1056 		if (!selected.empty() && isolateSelected)
1057 		{
1058 			for (auto* constellation : selected)
1059 			{
1060 				constellation->setFlagLines(linesDisplayed);
1061 			}
1062 		}
1063 		else
1064 		{
1065 			for (auto* constellation : constellations)
1066 			{
1067 				constellation->setFlagLines(linesDisplayed);
1068 			}
1069 		}
1070 		emit linesDisplayedChanged(displayed);
1071 	}
1072 }
1073 
getFlagLines(void) const1074 bool ConstellationMgr::getFlagLines(void) const
1075 {
1076 	return linesDisplayed;
1077 }
1078 
setFlagBoundaries(const bool displayed)1079 void ConstellationMgr::setFlagBoundaries(const bool displayed)
1080 {
1081 	if (boundariesDisplayed != displayed)
1082 	{
1083 		boundariesDisplayed = displayed;
1084 		if (!selected.empty() && isolateSelected)
1085 		{
1086 			for (auto* constellation : selected)
1087 			{
1088 				constellation->setFlagBoundaries(boundariesDisplayed);
1089 			}
1090 		}
1091 		else
1092 		{
1093 			for (auto* constellation : constellations)
1094 			{
1095 				constellation->setFlagBoundaries(boundariesDisplayed);
1096 			}
1097 		}
1098 		emit boundariesDisplayedChanged(displayed);
1099 	}
1100 }
1101 
getFlagBoundaries(void) const1102 bool ConstellationMgr::getFlagBoundaries(void) const
1103 {
1104 	return boundariesDisplayed;
1105 }
1106 
setFlagArt(const bool displayed)1107 void ConstellationMgr::setFlagArt(const bool displayed)
1108 {
1109 	if (artDisplayed != displayed)
1110 	{
1111 		artDisplayed = displayed;
1112 		if (!selected.empty() && isolateSelected)
1113 		{
1114 			for (auto* constellation : selected)
1115 			{
1116 				constellation->setFlagArt(artDisplayed);
1117 			}
1118 		}
1119 		else
1120 		{
1121 			for (auto* constellation : constellations)
1122 			{
1123 				constellation->setFlagArt(artDisplayed);
1124 			}
1125 		}
1126 		emit artDisplayedChanged(displayed);
1127 	}
1128 }
1129 
getFlagArt(void) const1130 bool ConstellationMgr::getFlagArt(void) const
1131 {
1132 	return artDisplayed;
1133 }
1134 
setFlagLabels(const bool displayed)1135 void ConstellationMgr::setFlagLabels(const bool displayed)
1136 {
1137 	if (namesDisplayed != displayed)
1138 	{
1139 		namesDisplayed = displayed;
1140 		if (!selected.empty() && isolateSelected)
1141 		{
1142 			for (auto* constellation : selected)
1143 				constellation->setFlagLabels(namesDisplayed);
1144 		}
1145 		else
1146 		{
1147 			for (auto* constellation : constellations)
1148 				constellation->setFlagLabels(namesDisplayed);
1149 		}
1150 		emit namesDisplayedChanged(displayed);
1151 	}
1152 }
1153 
getFlagLabels(void) const1154 bool ConstellationMgr::getFlagLabels(void) const
1155 {
1156 	return namesDisplayed;
1157 }
1158 
setFlagIsolateSelected(const bool isolate)1159 void ConstellationMgr::setFlagIsolateSelected(const bool isolate)
1160 {
1161 	if (isolateSelected != isolate)
1162 	{
1163 		isolateSelected = isolate;
1164 
1165 		// when turning off isolated selection mode, clear existing isolated selections.
1166 		if (!isolateSelected)
1167 		{
1168 			for (auto* constellation : constellations)
1169 			{
1170 				constellation->setFlagLines(getFlagLines());
1171 				constellation->setFlagLabels(getFlagLabels());
1172 				constellation->setFlagArt(getFlagArt());
1173 				constellation->setFlagBoundaries(getFlagBoundaries());
1174 			}
1175 		}
1176 		emit isolateSelectedChanged(isolate);
1177 	}
1178 }
1179 
getFlagIsolateSelected(void) const1180 bool ConstellationMgr::getFlagIsolateSelected(void) const
1181 {
1182 	return isolateSelected;
1183 }
1184 
setFlagConstellationPick(const bool mode)1185 void ConstellationMgr::setFlagConstellationPick(const bool mode)
1186 {
1187 	constellationPickEnabled = mode;
1188 }
1189 
getFlagConstellationPick(void) const1190 bool ConstellationMgr::getFlagConstellationPick(void) const
1191 {
1192 	return constellationPickEnabled;
1193 }
1194 
getSelected(void) const1195 StelObject* ConstellationMgr::getSelected(void) const
1196 {
1197 	return *selected.begin();  // TODO return all or just remove this method
1198 }
1199 
setSelected(const QString & abbreviation)1200 void ConstellationMgr::setSelected(const QString& abbreviation)
1201 {
1202 	Constellation * c = findFromAbbreviation(abbreviation);
1203 	if(c != Q_NULLPTR) setSelectedConst(c);
1204 }
1205 
setSelectedStar(const QString & abbreviation)1206 StelObjectP ConstellationMgr::setSelectedStar(const QString& abbreviation)
1207 {
1208 	Constellation * c = findFromAbbreviation(abbreviation);
1209 	if(c != Q_NULLPTR)
1210 	{
1211 		setSelectedConst(c);
1212 		return c->getBrightestStarInConstellation();
1213 	}
1214 	return Q_NULLPTR;
1215 }
1216 
setSelectedConst(Constellation * c)1217 void ConstellationMgr::setSelectedConst(Constellation * c)
1218 {
1219 	// update states for other constellations to fade them out
1220 	if (c != Q_NULLPTR)
1221 	{
1222 		selected.push_back(c);
1223 
1224 		if (isolateSelected)
1225 		{
1226 			if (!getFlagConstellationPick())
1227 			{
1228 				// Propagate current settings to newly selected constellation
1229 				c->setFlagLines(getFlagLines());
1230 				c->setFlagLabels(getFlagLabels());
1231 				c->setFlagArt(getFlagArt());
1232 				c->setFlagBoundaries(getFlagBoundaries());
1233 
1234 				for (auto* constellation : constellations)
1235 				{
1236 					bool match = false;
1237 					for (auto* selected_constellation : selected)
1238 					{
1239 						if (constellation == selected_constellation)
1240 						{
1241 							match=true; // this is a selected constellation
1242 							break;
1243 						}
1244 					}
1245 
1246 					if(!match)
1247 					{
1248 						// Not selected constellation
1249 						constellation->setFlagLines(false);
1250 						constellation->setFlagLabels(false);
1251 						constellation->setFlagArt(false);
1252 						constellation->setFlagBoundaries(false);
1253 					}
1254 				}
1255 			}
1256 			else
1257 			{
1258 				for (auto* constellation : constellations)
1259 				{
1260 					constellation->setFlagLines(false);
1261 					constellation->setFlagLabels(false);
1262 					constellation->setFlagArt(false);
1263 					constellation->setFlagBoundaries(false);
1264 				}
1265 
1266 				// Propagate current settings to newly selected constellation
1267 				c->setFlagLines(getFlagLines());
1268 				c->setFlagLabels(getFlagLabels());
1269 				c->setFlagArt(getFlagArt());
1270 				c->setFlagBoundaries(getFlagBoundaries());
1271 			}
1272 
1273 			Constellation::singleSelected = true;  // For boundaries
1274 		}
1275 		else
1276 			Constellation::singleSelected = false; // For boundaries
1277 	}
1278 	else
1279 	{
1280 		if (selected.empty()) return;
1281 
1282 		// Otherwise apply standard flags to all constellations
1283 		for (auto* constellation : constellations)
1284 		{
1285 			constellation->setFlagLines(getFlagLines());
1286 			constellation->setFlagLabels(getFlagLabels());
1287 			constellation->setFlagArt(getFlagArt());
1288 			constellation->setFlagBoundaries(getFlagBoundaries());
1289 		}
1290 
1291 		// And remove all selections
1292 		selected.clear();
1293 	}
1294 }
1295 
1296 //! Remove a constellation from the selected constellation list
unsetSelectedConst(Constellation * c)1297 void ConstellationMgr::unsetSelectedConst(Constellation * c)
1298 {
1299 	if (c != Q_NULLPTR)
1300 	{
1301 		for (auto iter = selected.begin(); iter != selected.end();)
1302 		{
1303 			if( (*iter)->getEnglishName() == c->getEnglishName() )
1304 			{
1305 				iter = selected.erase(iter);
1306 			}
1307 			else
1308 			{
1309 				++iter;
1310 			}
1311 		}
1312 
1313 		// If no longer any selection, restore all flags on all constellations
1314 		if (selected.empty())
1315 		{
1316 			// Otherwise apply standard flags to all constellations
1317 			for (auto* constellation : constellations)
1318 			{
1319 				constellation->setFlagLines(getFlagLines());
1320 				constellation->setFlagLabels(getFlagLabels());
1321 				constellation->setFlagArt(getFlagArt());
1322 				constellation->setFlagBoundaries(getFlagBoundaries());
1323 			}
1324 
1325 			Constellation::singleSelected = false; // For boundaries
1326 		}
1327 		else if(isolateSelected)
1328 		{
1329 			// No longer selected constellation
1330 			c->setFlagLines(false);
1331 			c->setFlagLabels(false);
1332 			c->setFlagArt(false);
1333 			c->setFlagBoundaries(false);
1334 
1335 			Constellation::singleSelected = true;  // For boundaries
1336 		}
1337 	}
1338 }
1339 
loadBoundaries(const QString & boundaryFile)1340 bool ConstellationMgr::loadBoundaries(const QString& boundaryFile)
1341 {
1342 	Constellation *cons = Q_NULLPTR;
1343 
1344 	// delete existing boundaries if any exist
1345 	for (auto* segment : allBoundarySegments)
1346 	{
1347 		delete segment;
1348 	}
1349 	allBoundarySegments.clear();
1350 
1351 	qDebug() << "Loading constellation boundary data ... ";
1352 
1353 	// Modified boundary file by Torsten Bronger with permission
1354 	// http://pp3.sourceforge.net
1355 	QFile dataFile(boundaryFile);
1356 	if (!dataFile.open(QIODevice::ReadOnly | QIODevice::Text))
1357 	{
1358 		qWarning() << "Boundary file " << QDir::toNativeSeparators(boundaryFile) << " not found";
1359 		return false;
1360 	}
1361 
1362 	double DE, RA;
1363 	Vec3d XYZ;
1364 	unsigned numc;
1365 	vector<Vec3d> *points = Q_NULLPTR;
1366 	QString consname, data = "";
1367 	unsigned int i = 0;
1368 
1369 	// Added support of comments for constellation_boundaries.dat file
1370 	QRegularExpression commentRx("^(\\s*#.*|\\s*)$");
1371 	while (!dataFile.atEnd())
1372 	{
1373 		// Read the line
1374 		QString record = QString::fromUtf8(dataFile.readLine());
1375 
1376 		// Skip comments
1377 		if (commentRx.match(record).hasMatch())
1378 			continue;
1379 
1380 		// Append the data
1381 		data.append(record);
1382 	}
1383 
1384 	// Read and parse the data without comments
1385 	QTextStream istr(&data);
1386 	while (!istr.atEnd())
1387 	{
1388 		unsigned num = 0;
1389 		istr >> num;
1390 		if(num == 0)
1391 			continue; // empty line
1392 
1393 		points = new vector<Vec3d>;
1394 
1395 		for (unsigned int j=0;j<num;j++)
1396 		{
1397 			istr >> RA >> DE;
1398 
1399 			RA*=M_PI/12.;     // Convert from hours to rad
1400 			DE*=M_PI/180.;    // Convert from deg to rad
1401 
1402 			// Calc the Cartesian coord with RA and DE
1403 			StelUtils::spheToRect(RA,DE,XYZ);
1404 			points->push_back(XYZ);
1405 		}
1406 
1407 		// this list is for the de-allocation
1408 		allBoundarySegments.push_back(points);
1409 
1410 		istr >> numc;
1411 		// there are 2 constellations per boundary
1412 
1413 		for (unsigned int j=0;j<numc;j++)
1414 		{
1415 			istr >> consname;
1416 			// not used?
1417 			if (consname == "SER1" || consname == "SER2") consname = "SER";
1418 
1419 			cons = findFromAbbreviation(consname);
1420 			if (!cons)
1421 				qWarning() << "ERROR while processing boundary file - cannot find constellation: " << consname;
1422 			else
1423 				cons->isolatedBoundarySegments.push_back(points);
1424 		}
1425 
1426 		if (cons)
1427 		{
1428 			cons->sharedBoundarySegments.push_back(points);
1429 			points=Q_NULLPTR; // Avoid Coverity resource leak warning. (CID48925).
1430 		}
1431 		i++;
1432 	}
1433 	dataFile.close();
1434 	qDebug() << "Loaded" << i << "constellation boundary segments";
1435 	if (points)
1436 	{
1437 		delete points; // See if Coverity complains here? (CID48925).
1438 		points=Q_NULLPTR;
1439 	}
1440 
1441 	return true;
1442 }
1443 
drawBoundaries(StelPainter & sPainter,const Vec3d & obsVelocity) const1444 void ConstellationMgr::drawBoundaries(StelPainter& sPainter, const Vec3d &obsVelocity) const
1445 {
1446 	const float ppx = static_cast<float>(sPainter.getProjector()->getDevicePixelsPerPixel());
1447 	sPainter.setBlending(false);
1448 	if (constellationBoundariesThickness>1 || ppx>1.f)
1449 		sPainter.setLineWidth(constellationBoundariesThickness*ppx); // set line thickness
1450 	sPainter.setLineSmooth(true);
1451 	for (auto* constellation : constellations)
1452 	{
1453 		constellation->drawBoundaryOptim(sPainter, obsVelocity);
1454 	}
1455 	if (constellationBoundariesThickness>1 || ppx>1.f)
1456 		sPainter.setLineWidth(1); // restore line thickness
1457 	sPainter.setLineSmooth(false);
1458 }
1459 
searchByNameI18n(const QString & nameI18n) const1460 StelObjectP ConstellationMgr::searchByNameI18n(const QString& nameI18n) const
1461 {
1462 	QString objw = nameI18n.toUpper();
1463 
1464 	for (auto* constellation : constellations)
1465 	{
1466 		QString objwcap = constellation->nameI18.toUpper();
1467 		if (objwcap == objw) return constellation;
1468 	}
1469 	return Q_NULLPTR;
1470 }
1471 
searchByName(const QString & name) const1472 StelObjectP ConstellationMgr::searchByName(const QString& name) const
1473 {
1474 	QString objw = name.toUpper();
1475 	for (auto* constellation : constellations)
1476 	{
1477 		QString objwcap = constellation->englishName.toUpper();
1478 		if (objwcap == objw) return constellation;
1479 
1480 		objwcap = constellation->abbreviation.toUpper();
1481 		if (objwcap == objw) return constellation;
1482 	}
1483 	return Q_NULLPTR;
1484 }
1485 
searchByID(const QString & id) const1486 StelObjectP ConstellationMgr::searchByID(const QString &id) const
1487 {
1488 	for (auto* constellation : constellations)
1489 	{
1490 		if (constellation->getID() == id) return constellation;
1491 	}
1492 	return Q_NULLPTR;
1493 }
1494 
listAllObjects(bool inEnglish) const1495 QStringList ConstellationMgr::listAllObjects(bool inEnglish) const
1496 {
1497 	QStringList result;
1498 	if (inEnglish)
1499 	{
1500 		for (auto* constellation : constellations)
1501 		{
1502 			result << constellation->getEnglishName();
1503 		}
1504 	}
1505 	else
1506 	{
1507 		for (auto* constellation : constellations)
1508 		{
1509 			result << constellation->getNameI18n();
1510 		}
1511 	}
1512 	return result;
1513 }
1514 
getStelObjectType() const1515 QString ConstellationMgr::getStelObjectType() const
1516 {
1517 	return Constellation::CONSTELLATION_TYPE;
1518 }
1519 
setSelected(const StelObject * s)1520 void ConstellationMgr::setSelected(const StelObject *s)
1521 {
1522 	if (!s)
1523 		setSelectedConst(Q_NULLPTR);
1524 	else
1525 	{
1526 		if (StelApp::getInstance().getSkyCultureMgr().getCurrentSkyCultureBoundariesIdx()==0) // generic IAU boundaries
1527 			setSelectedConst(isObjectIn(s));
1528 		else
1529 			setSelectedConst(isStarIn(s));
1530 	}
1531 }
1532 
isObjectIn(const StelObject * s) const1533 Constellation* ConstellationMgr::isObjectIn(const StelObject *s) const
1534 {
1535 	StelCore *core = StelApp::getInstance().getCore();
1536 	QString IAUConst = core->getIAUConstellation(s->getEquinoxEquatorialPos(core));
1537 	for (auto* constellation : constellations)
1538 	{
1539 		// Check if the object is in the constellation
1540 		if (constellation->getShortName().toUpper() == IAUConst.toUpper())
1541 			return constellation;
1542 	}
1543 	return Q_NULLPTR;
1544 }
1545