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