• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 17068063291

19 Aug 2025 11:22AM UTC coverage: 11.766%. Remained the same
17068063291

push

github

alex-w
Reformatting

14706 of 124990 relevant lines covered (11.77%)

18303.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/core/modules/Constellation.cpp
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
#include "StelProjector.hpp"
22
#include "Constellation.hpp"
23
#include "StarMgr.hpp"
24
#include "StelModuleMgr.hpp"
25
#include "NebulaMgr.hpp"
26

27
#include "StelTexture.hpp"
28
#include "StelPainter.hpp"
29
#include "StelApp.hpp"
30
#include "StelCore.hpp"
31
#include "StelUtils.hpp"
32
#include "ConstellationMgr.hpp"
33
#include "StelSkyCultureMgr.hpp"
34
#include "ZoneArray.hpp"
35
#include "StelModuleMgr.hpp"
36
#include "StelSkyCultureMgr.hpp"
37

38
#include <QString>
39
#include <QJsonArray>
40
#include <QTextStream>
41
#include <QDebug>
42
#include <QFontMetrics>
43
#include <QIODevice>
44

45
const QString Constellation::CONSTELLATION_TYPE = QStringLiteral("Constellation");
46

47
Vec3f Constellation::lineColor = Vec3f(0.4f,0.4f,0.8f);
48
Vec3f Constellation::labelColor = Vec3f(0.4f,0.4f,0.8f);
49
Vec3f Constellation::boundaryColor = Vec3f(0.8f,0.3f,0.3f);
50
Vec3f Constellation::hullColor = Vec3f(0.6f,0.2f,0.2f);
51
bool Constellation::singleSelected = false;
52
bool Constellation::seasonalRuleEnabled = false;
53
float Constellation::artIntensityFovScale = 1.0f;
54

55
Constellation::Constellation()
×
56
        : numberOfSegments(0)
×
57
        , beginSeason(0)
×
58
        , endSeason(0)
×
59
        , singleStarConstellationRadius(cos(M_PI/360.)) // default radius of 1/2 degrees
×
60
        , convexHull(nullptr)
×
61
        , artOpacity(1.f)
×
62
{
63
}
×
64

65
Constellation::~Constellation()
×
66
{
67
}
×
68

69
bool Constellation::read(const QJsonObject& data, StarMgr *starMgr)
×
70
{
71
        static NebulaMgr *nebulaMgr=GETSTELMODULE(NebulaMgr);
×
72
        static StelCore *core=StelApp::getInstance().getCore();
×
73
        const QString id = data["id"].toString();
×
74
        const QStringList idParts = id.split(" ");
×
75
        if (idParts.size() == 3 && idParts[0] == "CON")
×
76
        {
77
                abbreviation = idParts[2].trimmed();
×
78
        }
79
        else
80
        {
81
                qWarning().nospace() << "Bad constellation id: expected \"CON cultureName Abbrev\", got " << id;
×
82
                return false;
×
83
        }
84

85
        const QJsonValue names = data["common_name"].toObject();
×
86
        culturalName.translated = names["english"].toString().trimmed();
×
87
        culturalName.byname = names["byname"].toString().trimmed();
×
88
        culturalName.native = names["native"].toString().trimmed();
×
89
        culturalName.pronounce = names["pronounce"].toString().trimmed();
×
90
        culturalName.IPA = names["IPA"].toString().trimmed();
×
91
        culturalName.transliteration = names["transliteration"].toString().trimmed();
×
92

93
        context = names["context"].toString().trimmed();
×
94
        if (culturalName.translated.isEmpty() && culturalName.native.isEmpty() && culturalName.pronounce.isEmpty())
×
95
                qWarning() << "No name for constellation" << id;
×
96

97
        constellation.clear();
×
98
        const QJsonArray &linesArray=data["lines"].toArray();
×
99
        if (linesArray.isEmpty())
×
100
        {
101
                qWarning().nospace() << "Empty lines array found for constellation " << id << " (" << culturalName.native << ")";
×
102
                return false;
×
103
        }
104

105
        const bool isDarkConstellation = (!linesArray[0].toArray().isEmpty() && linesArray[0].toArray()[0].isArray());
×
106
        if (isDarkConstellation)
×
107
        {
108
                for (const auto& polyLineObj : linesArray) // auto=[[ra, dec], [ra, dec], ...]
×
109
                {
110
                        const auto& polyLine = polyLineObj.toArray();
×
111
                        if (polyLine.size() < 2) continue; // one point doesn't define a segment
×
112

113
                        const auto numSegments = polyLine.size() - 1;
×
114
                        dark_constellation.reserve(dark_constellation.size() + 2 * numSegments);
×
115

116
                        Vec3d prevPoint = Vec3d(0.);
×
117
                        int darkConstLineIndex=0;
×
118
                        for (qsizetype i = 0; i < polyLine.size(); ++i)
×
119
                        {
120
                                if (polyLine[i].isString())
×
121
                                {
122
                                        // Can be "thin" or "bold", but we don't support these modifiers yet, so ignore this entry
123
                                        const auto s = polyLine[i].toString();
×
124
                                        if (s == "thin" || s == "bold")
×
125
                                                continue;
×
126
                                }
×
127

128
                                if (!polyLine[i].isArray())
×
129
                                {
130
                                        qWarning().nospace() << "Error in constellation " << id << ": bad point"
×
131
                                                             << polyLine[i].toString() << ": isn't an array of two numbers (RA and dec)";
×
132
                                        return false;
×
133
                                }
134
                                const QJsonArray arr = polyLine[i].toArray();
×
135
                                if (arr.size() != 2 || !arr[0].isDouble() || !arr[1].isDouble())
×
136
                                {
137
                                        qWarning().nospace() << "Error in constellation " << id << ": bad point"
×
138
                                                             << polyLine[i].toString() << ": isn't an array of two numbers (RA and dec)";
×
139
                                        return false;
×
140
                                }
141

142
                                const double RA = arr[0].toDouble() * (M_PI_180*15.);
×
143
                                const double DE = arr[1].toDouble() * M_PI_180;
×
144
                                Vec3d newPoint;
×
145
                                StelUtils::spheToRect(RA, DE, newPoint);
×
146

147
                                if (prevPoint != Vec3d(0.))
×
148
                                {
149
                                        dark_constellation.push_back(QSharedPointer<StelObject>(new CoordObject(QString("%1_%2.A").arg(abbreviation, QString::number(darkConstLineIndex)), prevPoint)));
×
150
                                        dark_constellation.push_back(QSharedPointer<StelObject>(new CoordObject(QString("%1_%2.B").arg(abbreviation, QString::number(darkConstLineIndex)), newPoint)));
×
151
                                }
152
                                prevPoint = newPoint;
×
153
                                ++darkConstLineIndex;
×
154
                        }
×
155
                }
×
156
                numberOfSegments = dark_constellation.size() / 2;
×
157
                // Name tag should go to constellation's centre of gravity
158
                Vec3d XYZname1(0.);
×
159
                for(unsigned int ii=0;ii<numberOfSegments*2;ii+=2)
×
160
                {
161
                        XYZname1 += dark_constellation[ii]->getJ2000EquatorialPos(nullptr);
×
162
                }
163
                XYZname1.normalize();
×
164
                XYZname.append(XYZname1);
×
165
        }
166
        else
167
        {
168
                for (const auto& polyLineObj : linesArray)
×
169
                {
170
                        const auto& polyLine = polyLineObj.toArray();
×
171
                        if (polyLine.size() < 2) continue; // one point doesn't define a segment
×
172

173
                        const auto numSegments = polyLine.size() - 1;
×
174
                        constellation.reserve(constellation.size() + 2 * numSegments);
×
175

176
                        StelObjectP prevPoint = nullptr;
×
177
                        for (qsizetype i = 0; i < polyLine.size(); ++i)
×
178
                        {
179
                                StelObjectP newPoint;
×
180
                                if (polyLine[i].isString() && polyLine[i].toString().startsWith("DSO:"))
×
181
                                {
182
                                        QString DSOname=polyLine[i].toString().remove(0,4);
×
183
                                        newPoint = nebulaMgr->searchByID(DSOname);
×
184
                                        if (!newPoint)
×
185
                                        {
186
                                                qWarning().nospace() << "Error in constellation " << abbreviation << ": can't find DSO " << DSOname << "... skipping constellation";
×
187
                                                return false;
×
188
                                        }
189
                                }
×
190
                                else
191
                                {
192
                                        if (polyLine[i].isString())
×
193
                                        {
194
                                                // Can be "thin" or "bold", but we don't support these modifiers yet, so ignore this entry
195
                                                const auto s = polyLine[i].toString();
×
196
                                                if (s == "thin" || s == "bold")
×
197
                                                        continue;
×
198
                                        }
×
199
                                        const StarId HP = StelUtils::getLongLong(polyLine[i]);
×
200
                                        if (HP > 0)
×
201
                                        {
202
                                                newPoint = HP <= NR_OF_HIP ? starMgr->searchHP(HP)
×
203
                                                                           : starMgr->searchGaia(HP);
×
204
                                                if (!newPoint)
×
205
                                                {
206
                                                        qWarning().nospace() << "Error in constellation " << abbreviation << ": can't find star HIP " << HP << "... skipping constellation";
×
207
                                                        return false;
×
208
                                                }
209
                                        }
210
                                }
211

212
                                if (prevPoint)
×
213
                                {
214
                                        constellation.push_back(prevPoint);
×
215
                                        constellation.push_back(newPoint);
×
216
                                }
217
                                prevPoint = newPoint;
×
218
                        }
×
219
                }
×
220

221
                numberOfSegments = constellation.size() / 2;
×
222
                if (data.contains("single_star_radius"))
×
223
                {
224
                        double rd = data["single_star_radius"].toDouble(0.5);
×
225
                        singleStarConstellationRadius = cos(rd*M_PI/180.);
×
226
                }
227

228
                // Name tag should go to constellation's centre of gravity
229
                Vec3d XYZname1(0.);
×
230
                for(unsigned int ii=0;ii<numberOfSegments*2;++ii)
×
231
                {
232
                        XYZname1 += constellation[ii]->getJ2000EquatorialPos(core);
×
233
                }
234
                XYZname1.normalize();
×
235
                XYZname.append(XYZname1);
×
236
        }
237

238
        // At this point we have either a constellation or a dark_constellation filled
239

240
        // Sometimes label placement is suboptimal. Allow a correction from the automatic solution in label_offset:[dRA_deg, dDec_deg]
241
        if (data.contains("label_offset"))
×
242
        {
243
                QJsonArray offset=data["label_offset"].toArray();
×
244
                if (offset.size()!=2)
×
245
                        qWarning() << "Bad constellation label offset given for " << id << ", ignoring";
×
246
                else
247
                {
248
                        double ra, dec;
249
                        StelUtils::rectToSphe(&ra, &dec, XYZname[0]);
×
250
                        ra  += offset[0].toDouble()*M_PI_180;
×
251
                        dec += offset[1].toDouble()*M_PI_180;
×
252
                        StelUtils::spheToRect(ra, dec, XYZname[0]);
×
253
                }
254
        }
×
255
        // Manual label placement: Manual positions can have more than one (e.g., Serpens has two parts). In this case, discard automatically derived position.
256
        if (data.contains("label_positions"))
×
257
        {
258
                XYZname.clear();
×
259
                const QJsonArray &labelPosArray=data["label_positions"].toArray();
×
260
                for (const auto& labelPos : labelPosArray)
×
261
                {
262
                        const auto& labelArray=labelPos.toArray();
×
263
                        if (labelArray.size() != 2)
×
264
                        {
265
                                qWarning() << "Bad label position given for constellation" << id << "... skipping";
×
266
                                continue;
×
267
                        }
268
                        const double RA = labelArray[0].toDouble() * (M_PI_180*15.);
×
269
                        const double DE = labelArray[1].toDouble() * M_PI_180;
×
270
                        Vec3d newPoint;
×
271
                        StelUtils::spheToRect(RA, DE, newPoint);
×
272
                        XYZname.append(newPoint);
×
273
                }
×
274
        }
×
275

276
        //qDebug() << "Convex hull for " << englishName;
277
        if (data.contains("hull_extension"))
×
278
        {
279
                const QJsonArray &hullExtraArray=data["hull_extension"].toArray();
×
280

281
                for (qsizetype i = 0; i < hullExtraArray.size(); ++i)
×
282
                {
283
                        if (hullExtraArray[i].isString() && hullExtraArray[i].toString().startsWith("DSO:"))
×
284
                        {
285
                                QString DSOname=hullExtraArray[i].toString().remove(0,4);
×
286
                                const StelObjectP newDSO = nebulaMgr->searchByID(DSOname);
×
287
                                if (!newDSO)
×
288
                                {
289
                                        qWarning().nospace() << "Error in hull_extension for constellation " << abbreviation << ": can't find DSO " << DSOname << "... skipping";
×
290
                                }
291
                                else
292
                                        hullExtension.push_back(newDSO);
×
293
                        }
×
294
                        else
295
                        {
296
                                const StarId HP = StelUtils::getLongLong(hullExtraArray[i]);
×
297
                                if (HP > 0)
×
298
                                {
299
                                        const StelObjectP newStar = HP <= NR_OF_HIP ? starMgr->searchHP(HP)
×
300
                                                                                    : starMgr->searchGaia(HP);
×
301
                                        if (!newStar)
×
302
                                        {
303
                                                qWarning().nospace() << "Error in hull_extension for constellation " << abbreviation << ": can't find StarId " << HP << "... skipping";
×
304
                                        }
305
                                        else
306
                                                hullExtension.push_back(newStar);
×
307
                                }
×
308
                                else
309
                                {
310
                                        qWarning().nospace() << "Error in hull_extension for constellation " << abbreviation << ": bad element: " << hullExtraArray[i].toString() << "... skipping";
×
311
                                }
312
                        }
313
                }
314
        }
×
315
        hullRadius=data["hull_radius"].toDouble(data["single_star_radius"].toDouble(0.5));
×
316

317
        convexHull=makeConvexHull(constellation, hullExtension, dark_constellation, XYZname.constFirst(), hullRadius);
×
318

319
        beginSeason = 1;
×
320
        endSeason = 12;
×
321
        const auto visib = data["visibility"];
×
322
        if (visib.isUndefined()) return true;
×
323
        const auto visibility = visib.toObject();
×
324
        const auto months = visibility["months"].toArray();
×
325
        if (months.size() != 2)
×
326
        {
327
                qWarning() << "Unexpected format of \"visibility\" entry in constellation" << id;
×
328
                return true; // not critical
×
329
        }
330
        beginSeason = months[0].toInt();
×
331
        endSeason = months[1].toInt();
×
332
        seasonalRuleEnabled = true;
×
333

334
        return true;
×
335
}
×
336

337
QString Constellation::getScreenLabel() const
×
338
{
339
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
340
        return getCultureLabel(scMgr->getScreenLabelStyle());
×
341
}
342
QString Constellation::getInfoLabel() const
×
343
{
344
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
345
        return getCultureLabel(scMgr->getInfoLabelStyle());
×
346
}
347

348
QString Constellation::getCultureLabel(StelObject::CulturalDisplayStyle style) const
×
349
{
350
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
351
        return scMgr->createCulturalLabel(culturalName, style, getNameI18n(), abbreviationI18n);
×
352
}
353

354
void Constellation::drawOptim(StelPainter& sPainter, const StelCore* core, const SphericalCap& viewportHalfspace) const
×
355
{
356
        if (lineFader.getInterstate()<=0.0001f)
×
357
                return;
×
358
        if (!isSeasonallyVisible())
×
359
                return;
×
360

361
        const bool isDarkConstellation = !(dark_constellation.empty());
×
362
        const float darkFactor = (isDarkConstellation? 0.6667f : 1.0f);
×
363
        sPainter.setColor(lineColor*darkFactor, lineFader.getInterstate());
×
364

365
        if (isDarkConstellation)
×
366
                for (unsigned int i=0;i<numberOfSegments;++i)
×
367
                {
368
                        Vec3d pos1=dark_constellation[2*i]->getJ2000EquatorialPos(core);
×
369
                        Vec3d pos2=dark_constellation[2*i+1]->getJ2000EquatorialPos(core);
×
370
                        //pos1.normalize();
371
                        //pos2.normalize();
372
                        sPainter.drawGreatCircleArc(pos1, pos2, &viewportHalfspace);
×
373
                }
374
        else
375
                for (unsigned int i=0;i<numberOfSegments;++i)
×
376
                {
377
                        Vec3d star1=constellation[2*i]->getJ2000EquatorialPos(core);
×
378
                        Vec3d star2=constellation[2*i+1]->getJ2000EquatorialPos(core);
×
379
                        star1.normalize();
×
380
                        star2.normalize();
×
381
                        if (star1.fuzzyEquals(star2))
×
382
                        {
383
                                // draw single-star segment as circle
384
                                SphericalCap scCircle(star1, singleStarConstellationRadius);
×
385
                                sPainter.drawSphericalRegion(&scCircle, StelPainter::SphericalPolygonDrawModeBoundary);
×
386
                        }
×
387
                        else
388
                                sPainter.drawGreatCircleArc(star1, star2, &viewportHalfspace);
×
389
                }
390
}
391

392
// observer centered J2000 coordinates.
393
// These are either automatically computed from all stars forming the lines,
394
// or from the manually defined label point(s).
395
Vec3d Constellation::getJ2000EquatorialPos(const StelCore*) const
×
396
{
397
        if (XYZname.length() ==1)
×
398
                return XYZname.first();
×
399
        else
400
        {
401
                Vec3d point(0.0);
×
402
                for (Vec3d namePoint: XYZname)
×
403
                {
404
                        point += namePoint;
×
405
                }
406
                point.normalize();
×
407
                return point;
×
408
        }
409
}
410

411
void Constellation::drawName(const Vec3d &xyName, StelPainter& sPainter) const
×
412
{
413
        if (nameFader.getInterstate()==0.0f)
×
414
                return;
×
415

416
        // TODO: Find a solution of fallbacks when components are missing?
417
        if (isSeasonallyVisible())
×
418
        {
419
                QString name = getScreenLabel();
×
420
                sPainter.setColor(labelColor, nameFader.getInterstate());
×
421
                sPainter.drawText(static_cast<float>(xyName[0]), static_cast<float>(xyName[1]), name, 0., -sPainter.getFontMetrics().boundingRect(name).width()/2, 0, false);
×
422
        }
×
423
}
424

425
void Constellation::drawArtOptim(StelPainter& sPainter, const SphericalRegion& region, const Vec3d& obsVelocity) const
×
426
{
427
        if (isSeasonallyVisible())
×
428
        {
429
                const float intensity = artFader.getInterstate() * artOpacity * artIntensityFovScale;
×
430
                if (artTexture && intensity > 0.0f && region.intersects(boundingCap))
×
431
                {
432
                        sPainter.setColor(intensity,intensity,intensity);
×
433

434
                        // The texture is not fully loaded
435
                        if (artTexture->bind()==false)
×
436
                                return;
×
437

438
#ifdef Q_OS_LINUX
439
                        // Unfortunately applying aberration to the constellation artwork causes ugly artifacts visible on Linux.
440
                        // It is better to disable aberration in this case and have a tiny texture shift where it usually does not need to critically match.
441
                        sPainter.drawStelVertexArray(artPolygon, false, Vec3d(0.));
×
442
#else
443
                        sPainter.drawStelVertexArray(artPolygon, false, obsVelocity);
444
#endif
445
                }
446
        }
447
}
448

449
// Draw the art texture
450
void Constellation::drawArt(StelPainter& sPainter) const
×
451
{
452
        // Is this ever used?
453
        Q_ASSERT(0);
×
454
        sPainter.setBlending(true, GL_ONE, GL_ONE);
455
        sPainter.setCullFace(true);
456
        SphericalRegionP region = sPainter.getProjector()->getViewportConvexPolygon();
457
        drawArtOptim(sPainter, *region, Vec3d(0.));
458
        sPainter.setCullFace(false);
459
}
460

461
const Constellation* Constellation::isStarIn(const StelObject* s) const
×
462
{
463
        if (constellation.empty())
×
464
                return nullptr;
×
465

466
        for(unsigned int i=0;i<numberOfSegments*2;++i)
×
467
        {
468
                // constellation[i]==s test was not working
469
                if (constellation[i]->getID()==s->getID()) // don't compare englishNames, we may have duplicate names!
×
470
                {
471
                        // qDebug() << "Const matched. " << getEnglishName();
472
                        return this;
×
473
                }
474
        }
475
        return nullptr;
×
476
}
477

478
void Constellation::update(int deltaTime)
×
479
{
480
        lineFader.update(deltaTime);
×
481
        nameFader.update(deltaTime);
×
482
        artFader.update(deltaTime);
×
483
        boundaryFader.update(deltaTime);
×
484
        hullFader.update(deltaTime);
×
485
}
×
486

487
void Constellation::drawBoundaryOptim(StelPainter& sPainter, const Vec3d& obsVelocity) const
×
488
{
489
        if (boundaryFader.getInterstate()==0.0f)
×
490
                return;
×
491

492
        sPainter.setBlending(true);
×
493
        sPainter.setColor(boundaryColor, boundaryFader.getInterstate());
×
494

495
        unsigned int i, j;
496
        size_t size;
497
        std::vector<Vec3d> *points;
498

499
        if (singleSelected) size = isolatedBoundarySegments.size();
×
500
        else size = sharedBoundarySegments.size();
×
501

502
        const SphericalCap& viewportHalfspace = sPainter.getProjector()->getBoundingCap();
×
503

504
        for (i=0;i<size;i++)
×
505
        {
506
                if (singleSelected) points = isolatedBoundarySegments[i];
×
507
                else points = sharedBoundarySegments[i];
×
508

509
                for (j=0;j<points->size()-1;j++)
×
510
                {
511
                        Vec3d point0=points->at(j)+obsVelocity;
×
512
                        point0.normalize();
×
513
                        Vec3d point1=points->at(j+1)+obsVelocity;
×
514
                        point1.normalize();
×
515

516
                        sPainter.drawGreatCircleArc(point0, point1, &viewportHalfspace);
×
517
                }
518
        }
519
}
520

521
void Constellation::drawHullOptim(StelPainter& sPainter, const Vec3d& obsVelocity) const
×
522
{
523
        if (hullFader.getInterstate()==0.0f || (!convexHull) )
×
524
                return;
×
525

526
        sPainter.setBlending(true);
×
527
        sPainter.setColor(hullColor, hullFader.getInterstate());
×
528

529
        sPainter.drawSphericalRegion(convexHull.data(), StelPainter::SphericalPolygonDrawModeBoundary, nullptr, true, 5, obsVelocity); // draw nice with aberration
×
530
}
531

532

533
bool Constellation::isSeasonallyVisible() const
×
534
{
535
        // Is supported seasonal rules by current sky culture?
536
        if (!seasonalRuleEnabled)
×
537
                return true;
×
538

539
        bool visible = false;
×
540
        int year, month, day;
541
        // Get the current month
542
        StelUtils::getDateFromJulianDay(StelApp::getInstance().getCore()->getJD(), &year, &month, &day);
×
543
        if (endSeason >= beginSeason)
×
544
        {
545
                // OK, it's a "normal" season rule...
546
                if ((month >= beginSeason) && (month <= endSeason))
×
547
                        visible = true;
×
548
        }
549
        else
550
        {
551
                // ...oops, it's a "inverted" season rule
552
                if (((month>=1) && (month<=endSeason)) || ((month>=beginSeason) && (month<=12)))
×
553
                        visible = true;
×
554
        }
555
        return visible;
×
556
}
557

558
QString Constellation::getInfoString(const StelCore *core, const InfoStringGroup &flags) const
×
559
{
560
        Q_UNUSED(core)
561
        QString str;
×
562
        QTextStream oss(&str);
×
563

564
        if (flags&Name)
×
565
        {
566
                oss << "<h2>" << getInfoLabel() << "</h2>";
×
567
        }
568

569
        if (flags&ObjectType)
×
570
                oss << QString("%1: <b>%2</b>").arg(q_("Type"), getObjectTypeI18n()) << "<br />";
×
571

572
        oss << getSolarLunarInfoString(core, flags);
×
573
        postProcessInfoString(str, flags);
×
574

575
        return str;
×
576
}
×
577

578
StelObjectP Constellation::getBrightestStarInConstellation(void) const
×
579
{
580
        if (constellation.empty())
×
581
                return nullptr;
×
582

583
        float maxMag = 99.f;
×
584
        StelObjectP brightest;
×
585
        // maybe the brightest star has always odd index,
586
        // so check all segment endpoints:
587
        for (int i=2*static_cast<int>(numberOfSegments)-1;i>=0;i--)
×
588
        {
589
                const float Mag = constellation[i]->getVMagnitude(Q_NULLPTR);
×
590
                if (Mag < maxMag)
×
591
                {
592
                        brightest = constellation[i];
×
593
                        maxMag = Mag;
×
594
                }
595
        }
596
        for (unsigned int i=0; i<hullExtension.size();++i)
×
597
        {
598
                const float Mag = hullExtension[i]->getVMagnitude(Q_NULLPTR);
×
599
                if (Mag < maxMag)
×
600
                {
601
                        brightest = hullExtension[i];
×
602
                        maxMag = Mag;
×
603
                }
604
        }
605
        return brightest;
×
606
}
×
607

608
struct hullEntry
609
{
610
        StelObjectP obj;
611
        double x;
612
        double y;
613
};
614
// simple function only for ordering from Sedgewick 1990, Algorithms in C (p.353) used for sorting.
615
// The result fulfills the same purpose as some atan2, but with simpler operations.
616
static double theta(const hullEntry &p1, const hullEntry &p2)
×
617
{
618
        const double dx = p2.x-p1.x;
×
619
        const double ax = abs(dx);
×
620
        const double dy = p2.y-p1.y;
×
621
        const double ay = abs(dy);
×
622
        const double asum = ax+ay;
×
623
        double t = (asum==0) ? 0.0 : dy/asum;
×
624
        if (dx<0.)
×
625
                t=2-t;
×
626
        else if (dy<0.)
×
627
                t=4+t;
×
628
        return t*M_PI_2;
×
629
}
630

631
SphericalRegionP Constellation::makeConvexHull(const std::vector<StelObjectP> &starLines, const std::vector<StelObjectP> &hullExtension, const std::vector<StelObjectP> &darkLines, const Vec3d projectionCenter, const double hullRadius)
×
632
{
633
        static StelCore *core=StelApp::getInstance().getCore();
×
634
        // 1. Project every first star of a line pair (or just coordinates from a dark constellation) into a 2D tangential plane around projectionCenter.
635
        // Stars more than 90° from center cannot be projected of course and are skipped.
636
        double raC, deC;
637
        StelUtils::rectToSphe(&raC, &deC, projectionCenter);
×
638

639
        // starLines contains pairs of vertices, and some stars (or DSO) occur more than twice.
640
        QStringList idList;
×
641
        QList<StelObjectP>uniqueObjectList;
×
642
        foreach(auto &obj, starLines)
×
643
        {
644
                if (!idList.contains(obj->getID()))
×
645
                {
646
                        // Take this star into consideration. However, the "star"s may be pointers to the same star, we must compare IDs.
647
                        idList.append(obj->getID());
×
648
                        uniqueObjectList.append(obj);
×
649
                }
650
        }
×
651
        foreach(auto &obj, hullExtension)
×
652
        {
653
                if (!idList.contains(obj->getID()))
×
654
                {
655
                        // Take this star into consideration. However, the "star"s may be pointers to the same star, we must compare IDs.
656
                        idList.append(obj->getID());
×
657
                        uniqueObjectList.append(obj);
×
658
                }
659
        }
×
660
        foreach(auto &obj, darkLines)
×
661
        {
662
                if (!idList.contains(obj->getID()))
×
663
                {
664
                        // Take this coordinate into consideration. However, the elements may be pointers to the same direction, we must compare IDs.
665
                        idList.append(obj->getID());
×
666
                        uniqueObjectList.append(obj);
×
667
                }
668
        }
×
669

670
        QList<hullEntry> hullList;
×
671
        // Perspective (gnomonic) projection from Snyder 1987, Map Projections: A Working Manual (USGS).
672
        // we must use an almost-dummy object type with unique name.
673
        foreach(auto &obj, uniqueObjectList)
×
674
        {
675
                hullEntry entry;
×
676
                entry.obj=obj;
×
677
                double ra, de;
678
                StelUtils::rectToSphe(&ra, &de, entry.obj->getJ2000EquatorialPos(core));
×
679
                const double cosC=sin(deC)*sin(de) + cos(deC)*cos(de)*cos(ra-raC);
×
680
                if (cosC<=0.) // distance 90° or more from projection center? Discard!
×
681
                {
682
                        qWarning() << "Cannot include object" << entry.obj->getID() <<  "in convex hull: too far from projection center.";
×
683
                        continue;
×
684
                }
685
                const double kP=1./cosC;
×
686
                entry.x = -kP*cos(de)*sin(ra-raC); // x must be negative here.
×
687
                entry.y =  kP*(cos(deC)*sin(de)-sin(deC)*cos(de)*cos(ra-raC));
×
688
                hullList.append(entry);
×
689
                //qDebug().noquote().nospace() << "[ " << entry.x << " " << entry.y << " " << ra*M_180_PI/15 << " " << de*M_180_PI << " (" << entry.obj->getID() << ") ]"; // allows Postscript graphics, looks OK.
690
        }
×
691
        //qDebug() << "Hull candidates: " << hullList.length();
692
        if (hullList.count() < 3)
×
693
        {
694
                //qDebug() << "List length" << hullList.count() << " not enough for a convex hull... create circular area";
695
                SphericalRegionP res;
×
696
                switch (hullList.count())
×
697
                {
698
                        case 0:
×
699
                                res = new EmptySphericalRegion;
×
700
                                break;
×
701
                        case 1:
×
702
                                res = new SphericalCap(projectionCenter, cos(hullRadius*M_PI_180));
×
703
                                break;
×
704
                        case 2:
×
705
                                double halfDist=0.5*acos(hullList.at(0).obj->getJ2000EquatorialPos(core).dot(hullList.at(1).obj->getJ2000EquatorialPos(core)));
×
706
                                res = new SphericalCap(projectionCenter, cos(halfDist+hullRadius*M_PI_180));
×
707
                                break;
×
708
                }
709
                return res;
×
710
        }
×
711

712
        // 2. Apply Package Wrapping from Sedgewick 1990, Algorithms in C to find the outer points wrapping all points.
713
        // find minY
714
        int min=0;
×
715
        for(int i=1; i<hullList.count(); ++i)
×
716
        {
717
                const hullEntry &entry=hullList.at(i);
×
718
                if (entry.y<hullList.at(min).y)
×
719
                        min=i;
×
720
        }
721
        //qDebug() << "min entry is " << hullList.at(min).obj->getID();
722

723
        const int N=hullList.count(); // N...number of unique stars in constellation.
×
724
        //qDebug() << "unique stars N=" << N;
725
        hullList.append(hullList.at(min));
×
726

727
        //QStringList debugList;
728
        //// DUMP HULL LINE
729
        //for(int i=0; i<hullList.count(); ++i)
730
        //{
731
        //        const hullEntry &entry=hullList.at(i);
732
        //        debugList << entry.obj->getID();
733
        //}
734
        //qDebug() << "Hull candidate: " << debugList.join(" - ");
735
        //debugList.clear();
736

737
        int M=0;
×
738
        double th=0.0;
×
739
        for (M=0; M<N; ++M)
×
740
        {
741
#if (QT_VERSION<QT_VERSION_CHECK(5,13,0))
742
                std::swap(hullList[M], hullList[min]);
743
#else
744
                hullList.swapItemsAt(M, min);
×
745
#endif
746

747
                //// DUMP HULL LINE
748
                //for(int i=0; i<hullList.count(); ++i)
749
                //{
750
                //        const hullEntry &entry=hullList.at(i);
751
                //        debugList << entry.obj->getID();
752
                //        if (i==M)
753
                //                debugList << "|";
754
                //}
755
                //qDebug() << "Hull candidate after swap at M=" << M << debugList.join(" - ");
756
                //debugList.clear();
757

758
                min=N; double v=th; th=M_PI*2.0;
×
759
                for (int i=M+1; i<=N; ++i)
×
760
                {
761
                        //qDebug() << "From M:" << M << "(" << hullList.at(M).obj->getID() << ") to i: " << i << "=" << hullList.at(i).obj->getID() << ": th=" << theta(hullList.at(M), hullList.at(i)) * M_180_PI ;
762

763
                        if (theta(hullList.at(M), hullList.at(i)) > v)
×
764
                                if(theta(hullList.at(M), hullList.at(i))< th)
×
765
                                {
766
                                        min=i;
×
767
                                        th=theta(hullList.at(M), hullList.at(min));
×
768
                                        //qDebug() << "min:" << min << "th:" << th * M_180_PI;
769
                                }
770
                }
771

772
                if (min==N)
×
773
                {
774
                        //qDebug().nospace() << "min==N=" << N << ", we're done sorting. Hull should be of length M=" << M;
775
                        break; // now M+1 is holds number of "valid" hull points.
×
776
                }
777
        }
778
        ++M;
×
779
        //qDebug() << "Hull length" << M << "of" << hullList.count();
780
#if (QT_VERSION<QT_VERSION_CHECK(6,0,0))
781
        for (int e=0; e<=N-M; ++e) hullList.pop_back();
782
#else
783
        hullList.remove(M, N-M);
×
784
#endif
785
        //hullList.remove(M-1, N-M+1);
786

787
        //// DUMP HULL LINE
788
        //for(int i=0; i<hullList.count(); ++i)
789
        //{
790
        //        const hullEntry &entry=hullList.at(i);
791
        //        debugList << entry.obj->getID();
792
        //}
793
        //qDebug() << "Final Hull, M=" << M << debugList.join(" - ");
794
        //debugList.clear();
795

796
        //Now create a SphericalRegion
797
        QList<Vec3d> hullPoints;
×
798
        foreach(const hullEntry &entry, hullList)
×
799
        {
800
                Vec3d pos=entry.obj->getJ2000EquatorialPos(core);
×
801
                hullPoints.append(pos);
×
802
        }
×
803
#if (QT_VERSION<QT_VERSION_CHECK(6,0,0))
804
        SphericalPolygon *hull=new SphericalPolygon(hullPoints.toVector());
805
#else
806
        SphericalConvexPolygon *hull=new SphericalConvexPolygon(hullPoints);
×
807
#endif
808
        //qDebug() << "Successful hull:" << hull->toJSON();
809
        return hull;
×
810
}
×
811

812
void Constellation::makeConvexHull()
×
813
{
814
        // For 2-star automatic hulls, we must recreate the default XYZname (balance point) as hull circle center.
815
        if (constellation.size()==2 && XYZname.length()==1)
×
816
        {
817
                static StelCore *core=StelApp::getInstance().getCore();
×
818
                Vec3d XYZname1(0.);
×
819
                for(unsigned int i=0;i<constellation.size();++i)
×
820
                {
821
                        XYZname1 += constellation.at(i)->getJ2000EquatorialPos(core);
×
822
                }
823
                XYZname1.normalize();
×
824
                convexHull=makeConvexHull(constellation, hullExtension, dark_constellation, XYZname1, hullRadius);
×
825
        }
826
        else
827
                convexHull=makeConvexHull(constellation, hullExtension, dark_constellation, XYZname.constFirst(), hullRadius);
×
828
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc