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

Stellarium / stellarium / 15793453064

21 Jun 2025 06:37AM UTC coverage: 11.767% (-0.002%) from 11.769%
15793453064

push

github

10110111
Add support for touch clicks and moves

0 of 30 new or added lines in 1 file covered. (0.0%)

1336 existing lines in 5 files now uncovered.

14700 of 124924 relevant lines covered (11.77%)

18313.08 hits per line

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

0.0
/src/core/StelHips.cpp
1
/*
2
 * Stellarium
3
 * Copyright (C) 2017 Guillaume Chereau
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
 */
19

20
#include "StelHips.hpp"
21
#include "StelApp.hpp"
22
#include "StelCore.hpp"
23
#include "Planet.hpp"
24
#include "StelPainter.hpp"
25
#include "StelTextureMgr.hpp"
26
#include "StelUtils.hpp"
27
#include "StelProgressController.hpp"
28
#include "StelHealpix.hpp"
29

30
#include <QNetworkReply>
31
#include <QTimeLine>
32

33
class HipsTile
34
{
35
public:
36
        int order;
37
        int pix;
38
        StelTextureSP texture;
39
        StelTextureSP normalTexture;
40
        StelTextureSP horizonTexture;
41
        StelTextureSP allsky; // allsky low res version of the texture.
42
        StelTextureSP normalAllsky; // allsky low res version of the texture.
43
        StelTextureSP horizonAllsky; // allsky low res version of the texture.
44
};
45

UNCOV
46
static QString getExt(const QString& format)
×
47
{
UNCOV
48
        for (auto &ext : format.split(' '))
×
49
        {
UNCOV
50
                if (ext == "jpeg") return "jpg";
×
51
                if (ext == "png") return "png";
×
UNCOV
52
                if (ext == "webp") return "webp";
×
53
                if (ext == "tiff") return "tiff";
×
54
                if (ext == "bmp") return "bmp";
×
55
        }
×
56
        return QString();
×
57
}
58

59
QUrl HipsSurvey::getUrlFor(const QString& path) const
×
60
{
UNCOV
61
        QUrl base = url;
×
62
        QString args = "";
×
UNCOV
63
        if (base.scheme().isEmpty()) base.setScheme("file");
×
64
        if (base.scheme() != "file")
×
65
                args += QString("?v=%1").arg(static_cast<int>(releaseDate));
×
66
        return QString("%1/%2%3").arg(base.url(), path, args);
×
67
}
×
68

69
HipsSurvey::HipsSurvey(const QString& url_, const QString& frame, const QString& type,
×
70
                       const QMap<QString, QString>& hipslistProps, const double releaseDate_) :
×
UNCOV
71
        url(url_),
×
72
        type(type),
×
73
        hipsFrame(frame),
×
74
        releaseDate(releaseDate_),
×
75
        planetarySurvey(false),
×
76
        tiles(1000 * 512 * 512), // Cache max cost in pixels (enough for 1000 512x512 tiles).
×
77
        nbVisibleTiles(0),
×
78
        nbLoadedTiles(0)
×
79
{
80
        // First save properties from hipslist
81
        for (auto it = hipslistProps.begin(); it != hipslistProps.end(); ++it)
×
UNCOV
82
                properties[it.key()] = it.value();
×
UNCOV
83
        checkForPlanetarySurvey();
×
84

85
        // Immediately download the properties and replace the hipslist data with them.
86
        QNetworkRequest req = QNetworkRequest(getUrlFor("properties"));
×
UNCOV
87
        req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
×
UNCOV
88
        req.setRawHeader("User-Agent", StelUtils::getUserAgentString().toLatin1());
×
89
        QNetworkReply* networkReply = StelApp::getInstance().getNetworkAccessManager()->get(req);
×
90
        connect(networkReply, &QNetworkReply::finished, this, [&, networkReply] {
×
91
                QByteArray data = networkReply->readAll();
×
92
                for (const QByteArray &line : data.split('\n'))
×
93
                {
94
                        if (line.startsWith("#")) continue;
×
95
                        QStringList list=QString(line).split("=");
×
UNCOV
96
                        if (list.length()!=2) continue;
×
97
                        QString key = list.at(0).trimmed();
×
98
                        if (key.isEmpty()) continue;
×
99
                        QString value = list.at(1).trimmed();
×
100
                        properties[key] = value;
×
101
                }
×
102
                if (properties.contains("hips_release_date"))
×
103
                {
104
                        // XXX: StelUtils::getJulianDayFromISO8601String does not work
105
                        // without the seconds!
UNCOV
106
                        QDateTime date = QDateTime::fromString(properties["hips_release_date"].toString(), Qt::ISODate);
×
UNCOV
107
                        date.setTimeSpec(Qt::UTC);
×
UNCOV
108
                        releaseDate = StelUtils::qDateTimeToJd(date);
×
109
                }
×
110
                if (properties.contains("hips_frame"))
×
111
                        hipsFrame = properties["hips_frame"].toString().toLower();
×
112

113
                checkForPlanetarySurvey();
×
114
                emit propertiesChanged();
×
UNCOV
115
                emit statusChanged();
×
116
                networkReply->deleteLater();
×
117
        });
×
118
}
×
119

120
HipsSurvey::~HipsSurvey()
×
121
{
UNCOV
122
}
×
123

UNCOV
124
void HipsSurvey::checkForPlanetarySurvey()
×
125
{
UNCOV
126
        planetarySurvey = !QStringList{"equatorial","galactic","ecliptic"}.contains(hipsFrame, Qt::CaseInsensitive) ||
×
127
                          std::as_const(properties)["creator_did"].toString().contains("moon", Qt::CaseInsensitive) ||
×
UNCOV
128
                          std::as_const(properties)["client_category"].toString().contains("solar system", Qt::CaseInsensitive);
×
129
}
×
130

131
bool HipsSurvey::isVisible() const
×
132
{
UNCOV
133
        return static_cast<bool>(fader);
×
134
}
135

136
void HipsSurvey::setVisible(bool value)
×
137
{
UNCOV
138
        if (value == isVisible()) return;
×
139
        fader = value;
×
UNCOV
140
        if (!value && progressBar)
×
141
        {
142
                StelApp::getInstance().removeProgressBar(progressBar);
×
143
                progressBar = Q_NULLPTR;
×
144
        }
145
        emit visibleChanged(value);
×
146
}
147

148
int HipsSurvey::getPropertyInt(const QString& key, int fallback)
×
149
{
UNCOV
150
        if (!properties.contains(key)) return fallback;
×
151
        QJsonValue val = properties[key];
×
UNCOV
152
        if (val.isDouble()) return val.toInt();
×
153
        if (val.isString()) return val.toString().toInt();
×
154
        return 0;
×
155
}
×
156

157
bool HipsSurvey::getAllsky()
×
158
{
UNCOV
159
        if (!allsky.isNull() || noAllsky) return true;
×
160
        if (properties.isEmpty()) return false;
×
161

162
        // Allsky is deprecated after version 1.4.
163
        if (properties.contains("hips_version")) {
×
UNCOV
164
                QStringList version = properties["hips_version"].toString().split(".");
×
UNCOV
165
                if ((version.size() >= 2) && (version[0].toInt() * 100 + version[1].toInt() >= 104)) {
×
166
                        noAllsky = true;
×
167
                        return true;
×
168
                }
169
        }
×
170

UNCOV
171
        if (!networkReply)
×
172
        {
UNCOV
173
                QString ext = getExt(properties["hips_tile_format"].toString());
×
174
                QUrl path = getUrlFor(QString("Norder%1/Allsky.%2").arg(getPropertyInt("hips_order_min", 3)).arg(ext));
×
UNCOV
175
                qDebug() << "Load allsky" << path;
×
176
                QNetworkRequest req = QNetworkRequest(path);
×
177
                req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
×
178
                req.setRawHeader("User-Agent", StelUtils::getUserAgentString().toLatin1());
×
179
                networkReply = StelApp::getInstance().getNetworkAccessManager()->get(req);
×
180
                emit statusChanged();
×
181

182
                updateProgressBar(0, 100);
×
183
                connect(networkReply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) {
×
UNCOV
184
                        updateProgressBar(static_cast<int>(received), static_cast<int>(total));
×
185
                });
×
186
        }
×
187
        if (networkReply->isFinished())
×
188
        {
189
                updateProgressBar(100, 100);
×
190
                if (networkReply->error() == QNetworkReply::NoError) {
×
UNCOV
191
                        QByteArray data = networkReply->readAll();
×
192
                        qDebug().nospace() << "Got allsky for " << getTitle() << ", " << data.size() << " bytes";
×
193
                        allsky = QImage::fromData(data);
×
194
                        if (allsky.isNull())
×
195
                        {
196
                                qWarning() << "Failed to decode allsky image data";
×
197
                                noAllsky = true;
×
198
                        }
199
                } else {
×
200
                        noAllsky = true;
×
201
                }
202
                networkReply->deleteLater();
×
203
                networkReply = Q_NULLPTR;
×
UNCOV
204
                emit statusChanged();
×
205
        }
206
        return !allsky.isNull();
×
207
}
208

209
bool HipsSurvey::isLoading(void) const
×
210
{
UNCOV
211
        return (networkReply != Q_NULLPTR);
×
212
}
213

214
void HipsSurvey::setNormalsSurvey(const HipsSurveyP& normals)
×
215
{
UNCOV
216
        if (type != "planet")
×
217
        {
UNCOV
218
                qWarning() << "Attempted to add normals survey to a non-planet survey";
×
219
                return;
×
220
        }
221
        this->normals = normals;
×
222
}
223

224
void HipsSurvey::setHorizonsSurvey(const HipsSurveyP& horizons)
×
225
{
UNCOV
226
        if (type != "planet")
×
227
        {
UNCOV
228
                qWarning() << "Attempted to add horizons survey to a non-planet survey";
×
229
                return;
×
230
        }
231
        this->horizons = horizons;
×
232
}
233

234
void HipsSurvey::draw(StelPainter* sPainter, double angle, HipsSurvey::DrawCallback callback)
×
235
{
236
        // We don't draw anything until we get the properties file and the
237
        // allsky texture (if available).
UNCOV
238
        const bool outside = qFuzzyCompare(angle, 2.0 * M_PI);
×
UNCOV
239
        if (properties.isEmpty()) return;
×
UNCOV
240
        bool gotAllsky = getAllsky();
×
241
        if (normals) gotAllsky &= normals->getAllsky();
×
242
        if (horizons) gotAllsky &= horizons->getAllsky();
×
243
        if (!gotAllsky) return;
×
244
        if (fader.getInterstate() == 0.0f) return;
×
245
        sPainter->setColor(1, 1, 1, fader.getInterstate());
×
246

247
        // Set the projection.
248
        StelCore* core = StelApp::getInstance().getCore();
×
UNCOV
249
        StelCore::FrameType frame = StelCore::FrameUninitialized;
×
UNCOV
250
        if (hipsFrame == "galactic")
×
251
                frame = StelCore::FrameGalactic;
×
252
        else if (hipsFrame == "equatorial")
×
253
                frame = StelCore::FrameJ2000;
×
254
        if (frame)
×
255
                sPainter->setProjector(core->getProjection(frame));
×
256

257
        Vec3d obsVelocity(0.);
×
258
        // Aberration: retrieve observer velocity to apply, and transform it to frametype-dependent orientation
UNCOV
259
        if (core->getUseAberration())
×
260
        {
UNCOV
261
                static const Mat4d matVsop87ToGalactic=StelCore::matJ2000ToGalactic*StelCore::matVsop87ToJ2000;
×
262
                obsVelocity=core->getCurrentPlanet()->getHeliocentricEclipticVelocity(); // in VSOP87 frame...
×
UNCOV
263
                switch (frame){
×
264
                        case StelCore::FrameJ2000:
×
265
                                StelCore::matVsop87ToJ2000.transfo(obsVelocity);
×
266
                                break;
×
267
                        case StelCore::FrameGalactic:
×
268
                                matVsop87ToGalactic.transfo(obsVelocity);
×
269
                                break;
×
270
                        case StelCore::FrameHeliocentricEclipticJ2000:
×
271
                                // do nothing. Assume this frame is equal to VSOP87 (which is slightly incorrect!)
272
                                break;
×
273
                        default:
×
UNCOV
274
                                if (!planetarySurvey)
×
275
                                        qDebug() << "HiPS: Unexpected Frame: " << hipsFrame;
×
276
                }
277
                obsVelocity *= core->getAberrationFactor() * (AU/(86400.0*SPEED_OF_LIGHT));
×
278
        }
279

280
        // Compute the maximum visible level for the tiles according to the view resolution.
281
        // We know that each tile at level L represents an angle of 90 / 2^L
282
        // The maximum angle we want to see is the size of a tile in pixels time the angle for one visible pixel.
UNCOV
283
        double px = static_cast<double>(sPainter->getProjector()->getPixelPerRadAtCenter()) * angle;
×
UNCOV
284
        int tileWidth = getPropertyInt("hips_tile_width");
×
285

286
        int orderMin = getPropertyInt("hips_order_min", 3);
×
287
        if (order < 0)
×
288
        {
289
                order = getPropertyInt("hips_order");
×
290
                int normalsOrder = normals ? normals->getPropertyInt("hips_order") : order;
×
UNCOV
291
                int horizonsOrder = horizons ? horizons->getPropertyInt("hips_order") : order;
×
292
                if (normalsOrder < order)
×
293
                {
294
                        qWarning().nospace() << "Normal map survey's hips_order=" << normalsOrder << " is less than that of the color survey: "
×
295
                                             << order << ". Will reduce the total order accordingly.";
×
UNCOV
296
                        order = normalsOrder;
×
297
                }
298
                if (horizonsOrder < order)
×
299
                {
UNCOV
300
                        qWarning().nospace() << "Horizon map survey's hips_order=" << horizonsOrder << " is less than that of the color survey: "
×
301
                                             << order << ". Will reduce the total order accordingly.";
×
UNCOV
302
                        order = horizonsOrder;
×
303
                }
304
        }
305
        int drawOrder;
UNCOV
306
        if (outside)
×
307
        {
UNCOV
308
                drawOrder = ceil(log2(px / (4.0 * std::sqrt(2.0) * tileWidth)));
×
309
        }
310
        else
311
        {
312
                // The divisor approximately accounts for the fraction of the planetary disk
313
                // most often taken by the most stretched tiles (the ones near the poles).
UNCOV
314
                drawOrder = ceil(log2(px / 1.5 / tileWidth));
×
315
        }
UNCOV
316
        drawOrder = qBound(orderMin, drawOrder, order);
×
317
        int splitOrder = qMax(drawOrder, 4);
×
UNCOV
318
        if (tileWidth < 512)
×
319
        {
320
                int w = 512;
×
321
                while (tileWidth < w && splitOrder > 0)
×
322
                {
323
                        w /= 2;
×
324
                        --splitOrder;
×
325
                }
326
        }
327

UNCOV
328
        nbVisibleTiles = 0;
×
UNCOV
329
        nbLoadedTiles = 0;
×
330

331
        // Draw the 12 root tiles and their children.
332
        const SphericalCap& viewportRegion = sPainter->getProjector()->getBoundingCap();
×
UNCOV
333
        for (int i = 0; i < 12; i++)
×
334
        {
335
                drawTile(0, i, drawOrder, splitOrder, outside, viewportRegion, sPainter, obsVelocity, callback);
×
336
        }
337

338
        updateProgressBar(nbLoadedTiles, nbVisibleTiles);
×
339
}
340

341
void HipsSurvey::updateProgressBar(int nb, int total)
×
342
{
UNCOV
343
        if (nb == total && progressBar) {
×
344
                StelApp::getInstance().removeProgressBar(progressBar);
×
UNCOV
345
                progressBar = Q_NULLPTR;
×
346
        }
347
        if (nb == total) return;
×
348

UNCOV
349
        if (!progressBar)
×
350
        {
UNCOV
351
                progressBar = StelApp::getInstance().addProgressBar();
×
352
                progressBar->setFormat(getTitle());
×
UNCOV
353
                progressBar->setRange(0, 100);
×
354
        }
355
        progressBar->setValue(100 * nb / total);
×
356
}
357

358
HipsTile* HipsSurvey::getTile(int order, int pix)
×
359
{
UNCOV
360
        int nside = 1 << order;
×
361
        long int uid = pix + 4L * nside * nside;
×
UNCOV
362
        int orderMin = getPropertyInt("hips_order_min", 3);
×
363
        HipsTile* tile = tiles[uid];
×
364
        if (!tile)
×
365
        {
366
                StelTextureMgr& texMgr = StelApp::getInstance().getTextureManager();
×
367
                tile = new HipsTile();
×
UNCOV
368
                tile->order = order;
×
369
                tile->pix = pix;
×
370
                QString ext = getExt(properties["hips_tile_format"].toString());
×
371
                QUrl path = getUrlFor(QString("Norder%1/Dir%2/Npix%3.%4").arg(order).arg((pix / 10000) * 10000).arg(pix).arg(ext));
×
372
                const StelTexture::StelTextureParams texParams(true, GL_LINEAR, GL_CLAMP_TO_EDGE, true);
×
373
                tile->texture = texMgr.createTextureThread(path.url(), texParams, false);
×
374

375
                // Use the allsky image until we load the full texture.
376
                if (order == orderMin && !allsky.isNull())
×
377
                {
UNCOV
378
                        int nbw = static_cast<int>(std::sqrt(12 * (1 << (2 * order))));
×
379
                        int x = (pix % nbw) * allsky.width() / nbw;
×
UNCOV
380
                        int y = (pix / nbw) * allsky.width() / nbw;
×
381
                        int s = allsky.width() / nbw;
×
382
                        QImage image = allsky.copy(x, y, s, s);
×
383
                        tile->allsky = texMgr.createTexture(image, texParams);
×
384
                }
×
385
                int tileWidth = getPropertyInt("hips_tile_width", 512);
×
386
                tiles.insert(uid, tile, static_cast<long>(tileWidth) * tileWidth);
×
387
        }
×
388

389
        if (tile && normals && !tile->normalTexture)
×
390
        {
UNCOV
391
                const auto normalsTile = normals->getTile(order, pix);
×
392
                if (normalsTile && (normalsTile->texture || normalsTile->allsky))
×
393
                {
394
                        tile->normalTexture = normalsTile->texture;
×
395
                        tile->normalAllsky = normalsTile->allsky;
×
396
                }
397
        }
398

UNCOV
399
        if (tile && horizons && !tile->horizonTexture)
×
400
        {
UNCOV
401
                const auto horizonsTile = horizons->getTile(order, pix);
×
402
                if (horizonsTile && (horizonsTile->texture || horizonsTile->allsky))
×
403
                {
404
                        tile->horizonTexture = horizonsTile->texture;
×
405
                        tile->horizonAllsky = horizonsTile->allsky;
×
406
                }
407
        }
408

UNCOV
409
        return tile;
×
410
}
411

412
// Test if a shape in clipping coordinate is clipped or not.
UNCOV
413
static bool isClipped(int n, double (*pos)[4])
×
414
{
415
    // The six planes equations:
416
    const int P[6][4] = {
×
417
        {-1, 0, 0, -1}, {1, 0, 0, -1},
418
        {0, -1, 0, -1}, {0, 1, 0, -1},
419
        {0, 0, -1, -1}, {0, 0, 1, -1}
420
    };
421
    int i, p;
UNCOV
422
    for (p = 0; p < 6; p++) {
×
UNCOV
423
        for (i = 0; i < n; i++) {
×
UNCOV
424
            if (    P[p][0] * pos[i][0] +
×
425
                    P[p][1] * pos[i][1] +
×
426
                    P[p][2] * pos[i][2] +
×
427
                    P[p][3] * pos[i][3] <= 0) {
×
428
                break;
×
429
            }
430
        }
431
        if (i == n) // All the points are outside a clipping plane.
×
UNCOV
432
            return true;
×
433
    }
434
    return false;
×
435
}
436

437
bool HipsSurvey::bindTextures(HipsTile& tile, const int orderMin, Vec2f& texCoordShift, float& texCoordScale, bool& tileIsLoaded)
×
438
{
UNCOV
439
        constexpr int colorTexUnit = 0;
×
440
        constexpr int normalTexUnit = 2;
×
UNCOV
441
        constexpr int horizonTexUnit = 4;
×
442

443
        if (normals && !tile.normalTexture && !tile.normalAllsky) return false;
×
444
        if (horizons && !tile.horizonTexture && !tile.horizonAllsky) return false;
×
445

446
        bool ok = tile.texture->bind(colorTexUnit);
×
447
        if (!ok) ok = tile.allsky && tile.allsky->bind(colorTexUnit);
×
UNCOV
448
        if (ok && normals)
×
449
        {
450
                ok = tile.normalTexture && tile.normalTexture->bind(normalTexUnit);
×
451
                if (!ok) ok = tile.normalAllsky && tile.normalAllsky->bind(normalTexUnit);
×
452
        }
453
        if (ok && horizons)
×
454
        {
UNCOV
455
                ok = tile.horizonTexture && tile.horizonTexture->bind(horizonTexUnit);
×
456
                if (!ok) ok = tile.horizonAllsky && tile.horizonAllsky->bind(horizonTexUnit);
×
457
        }
458

459
        if (!ok) tileIsLoaded = false;
×
460

UNCOV
461
        if (!ok && tile.order > orderMin)
×
462
        {
463
                // Current-level textures failed to bind, let's try the previous level
464
                const auto parentTile = getTile(tile.order - 1, tile.pix / 4);
×
UNCOV
465
                if (!parentTile) return false;
×
466
                assert(parentTile->order == tile.order - 1);
×
467
                assert(parentTile->order >= orderMin);
×
468

469
                static const Vec2f bottomLeftPartUV[] = {Vec2f(0,0.5), Vec2f(0,0), Vec2f(0.5,0.5), Vec2f(0.5,0)};
×
UNCOV
470
                const int pos = tile.pix % 4;
×
471
                // Like the multiplication of the texture transform by scale and shift matrix on the left:
472
                // transform = shift(bottomLeftPartUV[pos]) * scale(0.5) * transform
UNCOV
473
                texCoordShift = texCoordShift * 0.5f + bottomLeftPartUV[pos];
×
474
                texCoordScale *= 0.5f;
×
475

UNCOV
476
                ok = bindTextures(*parentTile, orderMin, texCoordShift, texCoordScale, tileIsLoaded);
×
UNCOV
477
                if (!ok) return false;
×
478
        }
479

480
        return ok;
×
481
}
482

483
void HipsSurvey::drawTile(int order, int pix, int drawOrder, int splitOrder, bool outside,
×
484
                          const SphericalCap& viewportShape, StelPainter* sPainter,
485
                          Vec3d observerVelocity, DrawCallback callback)
486
{
UNCOV
487
        Vec3d pos;
×
488
        Mat3d mat3;
×
489
        const Vec2d uv[4] = {Vec2d(0, 0), Vec2d(0, 1), Vec2d(1, 0), Vec2d(1, 1)};
×
490
        bool tileLoaded = true;
×
UNCOV
491
        Vec2f texCoordShift(0,0);
×
492
        float texCoordScale = 1;
×
493
        HipsTile *tile;
494
        int orderMin = getPropertyInt("hips_order_min", 3);
×
495
        QVector<Vec3d> vertsArray;
×
496
        QVector<Vec2f> texArray;
×
497
        QVector<uint16_t> indicesArray;
×
498
        int nb;
499
        Vec4f color = sPainter->getColor();
×
500
        float alpha;
501

502
        healpix_pix2vec(1 << order, pix, pos.v);
×
503

504
        // Check if the tile is visible.  For outside survey (fullsky), we
505
        // use bounding cap, otherwise we use proper tile clipping test.
UNCOV
506
        if (outside)
×
507
        {
508
                SphericalCap boundingCap;
×
509
                boundingCap.n = pos;
×
510
                boundingCap.d = cos(M_PI / 2.0 / (1 << order));
×
UNCOV
511
                if (!viewportShape.intersects(boundingCap)) return;
×
UNCOV
512
        }
×
513
        else
514
        {
515
                double clip_pos[4][4];
UNCOV
516
                healpix_get_mat3(1 << order, pix, reinterpret_cast<double(*)[3]>(mat3.r));
×
517
                auto proj = sPainter->getProjector();
×
518
                for (int i = 0; i < 4; i++)
×
519
                {
520
                        pos = mat3 * Vec3d(1 - uv[i][1], uv[i][0], 1.0);
×
521
                        healpix_xy2vec(pos.v, pos.v);
×
UNCOV
522
                        proj->projectInPlace(pos);
×
523
                        pos[0] = (pos[0] - proj->getViewportCenter()[0]) / proj->getViewportWidth() * 2.0;
×
524
                        pos[1] = (pos[1] - proj->getViewportCenter()[1]) / proj->getViewportHeight() * 2.0;
×
UNCOV
525
                        clip_pos[i][0] = pos[0];
×
526
                        clip_pos[i][1] = pos[1];
×
527
                        clip_pos[i][2] = 0.0;
×
528
                        clip_pos[i][3] = 1.0;
×
529
                }
530
                if (isClipped(4, clip_pos)) return;
×
531

532
                // Also check the culling.
UNCOV
533
                if (order > 0)
×
534
                {
UNCOV
535
                        Vec2d u(clip_pos[1][0] - clip_pos[0][0], clip_pos[1][1] - clip_pos[0][1]);
×
536
                        Vec2d v(clip_pos[2][0] - clip_pos[0][0], clip_pos[2][1] - clip_pos[0][1]);
×
537
                        u.normalize();
×
UNCOV
538
                        v.normalize();
×
539
                        // XXX: the error (0.5) depends on the order: the higher the order
540
                        // the lower the error should be.
UNCOV
541
                        if (u[0] * v[1] - u[1] * v[0] > 0.5) return;
×
542
                }
543
        }
×
544

UNCOV
545
        if (order < orderMin)
×
546
                goto skip_render;
×
547

UNCOV
548
        nbVisibleTiles++;
×
UNCOV
549
        tile = getTile(order, pix);
×
550

551
        if (!tile) return;
×
552
        if (!bindTextures(*tile, orderMin, texCoordShift, texCoordScale, tileLoaded))
×
UNCOV
553
                return;
×
554

555
        if (tileLoaded)
×
UNCOV
556
                nbLoadedTiles++;
×
557

558
        if (order < drawOrder) goto skip_render;
×
559

560
        // Actually draw the tile, as a single quad.
561
        alpha = color[3];
×
UNCOV
562
        if (alpha < 1.0f)
×
563
        {
564
                sPainter->setBlending(true);
×
UNCOV
565
                sPainter->setColor(color[0], color[1], color[2], alpha);
×
566
        }
567
        else
568
        {
UNCOV
569
                sPainter->setBlending(false);
×
570
                sPainter->setColor(1, 1, 1, 1);
×
571
        }
UNCOV
572
        sPainter->setCullFace(true);
×
UNCOV
573
        nb = fillArrays(order, pix, drawOrder, splitOrder, outside, sPainter, observerVelocity,
×
574
                        texCoordShift, texCoordScale, vertsArray, texArray, indicesArray);
575
        if (!callback) {
×
576
                sPainter->setArrays(vertsArray.constData(), texArray.constData());
×
UNCOV
577
                sPainter->drawFromArray(StelPainter::Triangles, nb, 0, true, indicesArray.constData());
×
578
        } else {
UNCOV
579
                callback(vertsArray, texArray, indicesArray);
×
580
        }
581

UNCOV
582
skip_render:
×
583
        // Draw the children.
584
        if (order < drawOrder)
×
585
        {
UNCOV
586
                for (int i = 0; i < 4; i++)
×
587
                {
UNCOV
588
                        drawTile(order + 1, pix * 4 + i, drawOrder, splitOrder, outside,
×
589
                                 viewportShape, sPainter, observerVelocity, callback);
590
                }
591
        }
592
        // Restore the painter color.
UNCOV
593
        sPainter->setColor(color);
×
UNCOV
594
}
×
595

596
int HipsSurvey::fillArrays(int order, int pix, int drawOrder, int splitOrder,
×
597
                           bool outside, StelPainter* sPainter, Vec3d observerVelocity,
598
                           const Vec2f& texCoordShift, const float texCoordScale,
599
                           QVector<Vec3d>& verts, QVector<Vec2f>& tex, QVector<uint16_t>& indices)
600
{
601
        Q_UNUSED(sPainter)
602
        Mat3d mat3;
×
603
        Vec3d pos;
×
604
        Vec2f texPos;
×
605
        // First of all, min() limits gridSize to <256, because otherwise squaring it will overflow index type, uint16_t.
606
        // But in practice 32 points per side seems already good enough, and getting to 128 points noticeably affects
607
        // performance, so the upper limit is lower.
UNCOV
608
        uint16_t gridSize = static_cast<uint16_t>(1 << std::min(5, splitOrder + drawOrder - order));
×
609
        uint16_t n = gridSize + 1;
×
610
        const uint16_t INDICES[2][6][2] = {
×
611
                {{0, 0}, {0, 1}, {1, 0}, {1, 1}, {1, 0}, {0, 1}},
612
                {{0, 0}, {1, 0}, {1, 1}, {1, 1}, {0, 1}, {0, 0}},
613
        };
614

UNCOV
615
        healpix_get_mat3(1 << order, pix, reinterpret_cast<double(*)[3]>(mat3.r));
×
616

617
        for (int i = 0; i < n; i++)
×
618
        {
619
                for (int j = 0; j < n; j++)
×
620
                {
621
                        texPos = Vec2f(static_cast<float>(i) / gridSize, static_cast<float>(j) / gridSize);
×
UNCOV
622
                        pos = mat3 * Vec3d(1.0 - static_cast<double>(j) / gridSize, static_cast<double>(i) / gridSize, 1.0);
×
623
                        healpix_xy2vec(pos.v, pos.v);
×
624

625
                        // Aberration: Assume pos=normalized vertex position on the sphere in HiPS frame equatorial/ecliptical/galactic. Velocity is already transformed to frame
UNCOV
626
                        if (!planetarySurvey)
×
627
                        {
UNCOV
628
                                pos+=observerVelocity;
×
629
                                pos.normalize();
×
630
                        }
631

UNCOV
632
                        verts << pos;
×
UNCOV
633
                        tex << texPos * texCoordScale + texCoordShift;
×
634
                }
635
        }
636
        for (uint16_t i = 0; i < gridSize; i++)
×
637
        {
638
                for (uint16_t j = 0; j < gridSize; j++)
×
639
                {
UNCOV
640
                        for (uint16_t k = 0; k < 6; k++)
×
641
                        {
642
                                indices << (INDICES[outside ? 1 : 0][k][1] + i) * n +
×
UNCOV
643
                                            INDICES[outside ? 1 : 0][k][0] + j;
×
644
                        }
645

646
                        // Check that the surface is convex. If it isn't, make it convex.
647

UNCOV
648
                        const auto p0 = verts[indices[indices.size() - 6 + 0]];
×
649
                        const auto p1 = verts[indices[indices.size() - 6 + 1]];
×
650
                        const auto p2 = verts[indices[indices.size() - 6 + 2]];
×
651

652
                        // Midpoint of the shared edge of two triangles.
UNCOV
653
                        const auto midPoint = outside ? (p0 + p2)/2. : (p1 + p2)/2.;
×
654
                        // A vertex that's not on the shared edge.
655
                        const auto outerVert = outside ? p1 : p0;
×
656
                        // The vector from an outer edge towards the midpoint of the shared edge.
UNCOV
657
                        const auto vecFromMidPointToOuterVert = outerVert - midPoint;
×
UNCOV
658
                        if(vecFromMidPointToOuterVert.dot(midPoint) > 0)
×
659
                        {
660
                                // The surface is concave. Swap some vertices to make it convex.
661
                                if(outside)
×
662
                                {
663
                                        indices[indices.size() - 4] = indices[indices.size() - 2];
×
UNCOV
664
                                        indices[indices.size() - 1] = indices[indices.size() - 5];
×
665
                                }
666
                                else
667
                                {
668
                                        indices[indices.size() - 4] = indices[indices.size() - 3];
×
669
                                        indices[indices.size() - 1] = indices[indices.size() - 6];
×
670
                                }
671
                        }
672
                }
673
        }
674
        return gridSize * gridSize * 6;
×
675
}
676

677
//! Parse a hipslist file into a list of surveys.
UNCOV
678
QList<HipsSurveyP> HipsSurvey::parseHipslist(const QString& data)
×
679
{
UNCOV
680
        QList<HipsSurveyP> ret;
×
681
        static const QString defaultFrame = "equatorial";
×
UNCOV
682
        for (const auto& entry : data.split(QRegularExpression("\n\\s*\n")))
×
683
        {
UNCOV
684
                QString url;
×
685
                QString type;
×
UNCOV
686
                QString frame = defaultFrame;
×
UNCOV
687
                QString status;
×
UNCOV
688
                double releaseDate = 0;
×
689
                QMap<QString, QString> hipslistProps;
×
690
                for (const auto &line : entry.split('\n'))
×
691
                {
692
                        if (line.startsWith('#')) continue;
×
693
                        QString key = line.section("=", 0, 0).trimmed();
×
694
                        QString value = line.section("=", 1, -1).trimmed();
×
695

696
                        hipslistProps[key] = value;
×
697

698
                        if (key == "hips_service_url")
×
699
                        {
700
                                url = value;
×
701
                                // special case: https://github.com/Stellarium/stellarium/issues/1276
702
                                if (url.contains("data.stellarium.org/surveys/dss")) continue;
×
703
                        }
704
                        else if (key == "hips_release_date")
×
705
                        {
706
                                // XXX: StelUtils::getJulianDayFromISO8601String does not work
707
                                // without the seconds!
UNCOV
708
                                QDateTime date = QDateTime::fromString(value, Qt::ISODate);
×
709
                                date.setTimeSpec(Qt::UTC);
×
UNCOV
710
                                releaseDate = StelUtils::qDateTimeToJd(date);
×
UNCOV
711
                        }
×
UNCOV
712
                        else if (key == "hips_frame")
×
UNCOV
713
                                frame = value.toLower();
×
UNCOV
714
                        else if (key == "type")
×
UNCOV
715
                                type = value.toLower();
×
UNCOV
716
                        else if (key == "hips_status")
×
UNCOV
717
                                status = value.toLower();
×
UNCOV
718
                }
×
UNCOV
719
                if(status.split(' ').contains("public"))
×
UNCOV
720
                        ret.append(HipsSurveyP(new HipsSurvey(url, frame, type, hipslistProps, releaseDate)));
×
UNCOV
721
        }
×
UNCOV
722
        return ret;
×
UNCOV
723
}
×
724

UNCOV
725
QString HipsSurvey::getTitle(void) const
×
726
{
727
        // Todo: add a fallback if the properties don't have a title.
UNCOV
728
        return properties["obs_title"].toString();
×
729
}
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