/* * Copyright (C) 2009 Matthew Gates * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "StelProjector.hpp" #include "StelPainter.hpp" #include "StelApp.hpp" #include "StelCore.hpp" #include "StelGui.hpp" #include "StelGuiItems.hpp" #include "StelLocation.hpp" #include "StelNavigator.hpp" #include "StelObjectMgr.hpp" #include "StelModuleMgr.hpp" #include "StelFileMgr.hpp" #include "StelTextureMgr.hpp" #include "StelIniParser.hpp" #include "Satellites.hpp" #include "Satellite.hpp" #include "Planet.hpp" #include "SolarSystem.hpp" #include "StelJsonParser.hpp" #include "SatellitesDialog.hpp" #include #include #include #include #include #include #include #include #include #include #include StelModule* SatellitesStelPluginInterface::getStelModule() const { return new Satellites(); } StelPluginInfo SatellitesStelPluginInterface::getPluginInfo() const { // Allow to load the resources when used as a static plugin Q_INIT_RESOURCE(Satellites); StelPluginInfo info; info.id = "Satellites"; info.displayedName = "Satellites"; info.authors = "Matthew Gates"; info.contact = "http://stellarium.org/"; info.description = "Prediction of artificial satellite positions in Earth orbit based on NORAD TLE data"; return info; } Q_EXPORT_PLUGIN2(Satellites, SatellitesStelPluginInterface) Satellites::Satellites() : pxmapGlow(NULL), pxmapOnIcon(NULL), pxmapOffIcon(NULL), toolbarButton(NULL), earth(NULL), defaultHintColor(0.,0.4,0.6), progressBar(NULL) { setObjectName("Satellites"); configDialog = new SatellitesDialog(); } void Satellites::deinit() { Satellite::hintTexture.clear(); texPointer.clear(); configDialog->setVisible(false); } Satellites::~Satellites() { delete configDialog; } void Satellites::init() { QSettings* conf = StelApp::getInstance().getSettings(); try { StelFileMgr::makeSureDirExistsAndIsWritable(StelFileMgr::getUserDir()+"/modules/Satellites"); // If no settings in the main config file, create with defaults if (!conf->childGroups().contains("Satellites")) { qDebug() << "Stellites::init no Satellites section exists in main config file - creating with defaults"; restoreDefaultConfigIni(); } // populate settings from main config file. readSettingsFromConfig(); satellitesJsonPath = StelFileMgr::findFile("modules/Satellites", (StelFileMgr::Flags)(StelFileMgr::Directory|StelFileMgr::Writable)) + "/satellites.json"; // Load and find resources used in the plugin texPointer = StelApp::getInstance().getTextureManager().createTexture("textures/pointeur5.png"); Satellite::hintTexture = StelApp::getInstance().getTextureManager().createTexture(":/satellites/satellite_hint.png"); // key bindings and other actions StelGui* gui = dynamic_cast(StelApp::getInstance().getGui()); gui->addGuiActions("actionShow_Satellite_ConfigDialog", "Satellite Config Dialog", "Alt+Z", "Plugin Key Bindings", true, false); gui->addGuiActions("actionShow_Satellite_Hints", "Satellite Hints", "Ctrl+Z", "Plugin Key Bindings", true, false); gui->getGuiActions("actionShow_Satellite_Hints")->setChecked(hintFader); gui->addGuiActions("actionShow_Satellite_Labels", "Satellite Labels", "Shift+Z", "Plugin Key Bindings", true, false); gui->getGuiActions("actionShow_Satellite_Labels")->setChecked(Satellite::showLabels); // Gui toolbar button pxmapGlow = new QPixmap(":/graphicGui/glow32x32.png"); pxmapOnIcon = new QPixmap(":/satellites/bt_satellites_on.png"); pxmapOffIcon = new QPixmap(":/satellites/bt_satellites_off.png"); toolbarButton = new StelButton(NULL, *pxmapOnIcon, *pxmapOffIcon, *pxmapGlow, gui->getGuiActions("actionShow_Satellite_Hints")); gui->getButtonBar()->addButton(toolbarButton, "065-pluginsGroup"); connect(gui->getGuiActions("actionShow_Satellite_ConfigDialog"), SIGNAL(toggled(bool)), configDialog, SLOT(setVisible(bool))); connect(gui->getGuiActions("actionShow_Satellite_Hints"), SIGNAL(toggled(bool)), this, SLOT(setFlagHints(bool))); connect(gui->getGuiActions("actionShow_Satellite_Labels"), SIGNAL(toggled(bool)), this, SLOT(setFlagLabels(bool))); } catch (std::runtime_error &e) { qWarning() << "Satellites::init error: " << e.what(); return; } // If the json file does not already exist, create it from the resource in the QT resource if(!QFileInfo(satellitesJsonPath).exists()) { qDebug() << "Satellites::init satellites.json does not exist - copying default file to " << satellitesJsonPath; restoreDefaultJsonFile(); } else { qDebug() << "Satellites::init using satellite.json file: " << satellitesJsonPath; } // create satellites according to content os satellites.json file readJsonFile(); // Set up download manager and the update schedule downloadMgr = new QNetworkAccessManager(this); connect(downloadMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(updateDownloadComplete(QNetworkReply*))); updateState = CompleteNoUpdates; updateTimer = new QTimer(this); updateTimer->setSingleShot(false); // recurring check for update updateTimer->setInterval(13000); // check once every 13 seconds to see if it is time for an update connect(updateTimer, SIGNAL(timeout()), this, SLOT(checkForUpdate())); updateTimer->start(); earth = GETSTELMODULE(SolarSystem)->getEarth(); GETSTELMODULE(StelObjectMgr)->registerStelObjectMgr(this); // Handle changes to the observer location: connect(StelApp::getInstance().getCore()->getNavigator(), SIGNAL(locationChanged(StelLocation)), this, SLOT(observerLocationChanged(StelLocation))); //Load the module's custom style sheets QFile styleSheetFile; styleSheetFile.setFileName(":/satellites/normalStyle.css"); if(styleSheetFile.open(QFile::ReadOnly|QFile::Text)) { normalStyleSheet = styleSheetFile.readAll(); } styleSheetFile.close(); styleSheetFile.setFileName(":/satellites/nightStyle.css"); if(styleSheetFile.open(QFile::ReadOnly|QFile::Text)) { nightStyleSheet = styleSheetFile.readAll(); } styleSheetFile.close(); } void Satellites::setStelStyle(const QString&) { configDialog->updateStyle(); } const StelStyle Satellites::getModuleStyleSheet(const StelStyle& style) { StelStyle pluginStyle(style); if (style.confSectionName == "color") { pluginStyle.qtStyleSheet.append(normalStyleSheet); } else { pluginStyle.qtStyleSheet.append(nightStyleSheet); } return pluginStyle; } double Satellites::getCallOrder(StelModuleActionName actionName) const { if (actionName==StelModule::ActionDraw) return StelApp::getInstance().getModuleMgr().getModule("StarMgr")->getCallOrder(actionName)+1.; return 0; } QList Satellites::searchAround(const Vec3d& av, double limitFov, const StelCore*) const { QList result; if (!hintFader || StelApp::getInstance().getCore()->getNavigator()->getCurrentLocation().planetName != earth->getEnglishName()) return result; Vec3d v(av); v.normalize(); double cosLimFov = cos(limitFov * M_PI/180.); Vec3d equPos; foreach(const SatelliteP& sat, satellites) { if (sat->initialized && sat->visible) { equPos = sat->XYZ; equPos.normalize(); if (equPos[0]*v[0] + equPos[1]*v[1] + equPos[2]*v[2]>=cosLimFov) { result.append(qSharedPointerCast(sat)); } } } return result; } StelObjectP Satellites::searchByNameI18n(const QString& nameI18n) const { if (!hintFader || StelApp::getInstance().getCore()->getNavigator()->getCurrentLocation().planetName != earth->getEnglishName()) return NULL; QString objw = nameI18n.toUpper(); foreach(const SatelliteP& sat, satellites) { if (sat->initialized && sat->visible) { if (sat->getNameI18n().toUpper() == nameI18n) return qSharedPointerCast(sat); } } return NULL; } StelObjectP Satellites::searchByName(const QString& englishName) const { if (!hintFader || StelApp::getInstance().getCore()->getNavigator()->getCurrentLocation().planetName != earth->getEnglishName()) return NULL; QString objw = englishName.toUpper(); foreach(const SatelliteP& sat, satellites) { if (sat->initialized && sat->visible) { if (sat->getEnglishName().toUpper() == englishName) return qSharedPointerCast(sat); } } return NULL; } QStringList Satellites::listMatchingObjectsI18n(const QString& objPrefix, int maxNbItem) const { QStringList result; if (!hintFader || StelApp::getInstance().getCore()->getNavigator()->getCurrentLocation().planetName != earth->getEnglishName()) return result; if (maxNbItem==0) return result; QString objw = objPrefix.toUpper(); foreach(const SatelliteP& sat, satellites) { if (sat->initialized && sat->visible) { if (sat->getNameI18n().toUpper().left(objw.length()) == objw) { result << sat->getNameI18n().toUpper(); } } } result.sort(); if (result.size()>maxNbItem) result.erase(result.begin()+maxNbItem, result.end()); return result; } bool Satellites::configureGui(bool show) { if (show) { StelGui* gui = dynamic_cast(StelApp::getInstance().getGui()); gui->getGuiActions("actionShow_Satellite_ConfigDialog")->setChecked(true); } return true; } void Satellites::restoreDefaults(void) { restoreDefaultConfigIni(); restoreDefaultJsonFile(); readSettingsFromConfig(); } void Satellites::restoreDefaultConfigIni(void) { QSettings* conf = StelApp::getInstance().getSettings(); conf->beginGroup("Satellites"); // delete all existing Satellite settings... conf->remove(""); conf->setValue("show_satellite_hints", true); conf->setValue("show_satellite_labels", true); conf->setValue("updates_enabled", true); conf->setValue("show_satellites", true); conf->setValue("hint_color", "0.0,0.4,0.6"); conf->setValue("hint_font_size", 10); conf->setValue("tle_url0", "http://celestrak.com/NORAD/elements/noaa.txt"); conf->setValue("tle_url1", "http://celestrak.com/NORAD/elements/goes.txt"); conf->setValue("tle_url2", "http://celestrak.com/NORAD/elements/gps-ops.txt"); conf->setValue("tle_url3", "http://celestrak.com/NORAD/elements/galileo.txt"); conf->setValue("tle_url4", "http://celestrak.com/NORAD/elements/visual.txt"); conf->setValue("tle_url5", "http://celestrak.com/NORAD/elements/amateur.txt"); conf->setValue("tle_url6", "http://celestrak.com/NORAD/elements/iridium.txt"); conf->setValue("tle_url7", "http://celestrak.com/NORAD/elements/tle-new.txt"); conf->setValue("update_frequency_hours", 72); conf->endGroup(); } void Satellites::restoreDefaultJsonFile(void) { QFile src(":/satellites/satellites.json"); if (!src.copy(satellitesJsonPath)) { qWarning() << "Satellites::restoreDefaultJsonFile cannot copy json resource to " + satellitesJsonPath; } else { qDebug() << "Satellites::init copied default satellites.json to " << satellitesJsonPath; // The resource is read only, and the new file inherits this... make sure the new file // is writable by the Stellarium process so that updates can be done. QFile dest(satellitesJsonPath); dest.setPermissions(dest.permissions() | QFile::WriteOwner); // Make sure that in the case where an online update has previously been done, but // the json file has been manually removed, that an update is schreduled in a timely // manner StelApp::getInstance().getSettings()->remove("Satellites/last_update"); } } void Satellites::readSettingsFromConfig(void) { QSettings* conf = StelApp::getInstance().getSettings(); conf->beginGroup("Satellites"); // populate updateUrls from tle_url? keys QRegExp keyRE("^tle_url\\d+$"); updateUrls.clear(); foreach(QString key, conf->childKeys()) { if (keyRE.exactMatch(key)) { QString s = conf->value(key, "").toString(); if (!s.isEmpty() && s!="") updateUrls << s; } } // updater related settings... updateFrequencyHours = conf->value("update_frequency_hours", 72).toInt(); lastUpdate = QDateTime::fromString(conf->value("last_update", "2009-01-01 12:00:00").toString(), "yyyy-MM-dd HH:mm:ss"); hintFader = conf->value("show_satellite_hints", true).toBool(); Satellite::showLabels = conf->value("show_satellite_labels", true).toBool(); updatesEnabled = conf->value("updates_enabled", true).toBool(); // Get a font for labels labelFont.setPixelSize(conf->value("hint_font_size", 10).toInt()); conf->endGroup(); } int Satellites::readJsonFile(void) { // First, delete all existing Satellite objects... satellites.clear(); QFile satelliteJsonFile(satellitesJsonPath); if (!satelliteJsonFile.open(QIODevice::ReadOnly)) { qWarning() << "Satellites::init cannot open " << satellitesJsonPath; return 0; } int numReadOk = 0; QVariantMap map; map = StelJsonParser::parse(&satelliteJsonFile).toMap(); QVariantList defaultHintColorMap; defaultHintColorMap << defaultHintColor[0] << defaultHintColor[1] << defaultHintColor[2]; if (map.contains("hintColor")) { defaultHintColorMap = map.value("hintColor").toList(); defaultHintColor.set(defaultHintColorMap.at(0).toDouble(), defaultHintColorMap.at(1).toDouble(), defaultHintColorMap.at(2).toDouble()); } QVariantMap satMap = map.value("satellites").toMap(); foreach(QString designation, satMap.keys()) { QVariantMap satData = satMap.value(designation).toMap(); satData["designation"] = designation; if (!satData.contains("hintColor")) satData["hintColor"] = defaultHintColorMap; SatelliteP sat(new Satellite(satData)); if (sat->initialized) { satellites.append(sat); numReadOk++; } } satelliteJsonFile.close(); return numReadOk; } QStringList Satellites::getGroups(void) const { QStringList groups; foreach (const SatelliteP& sat, satellites) { if (sat->initialized) { foreach(QString group, sat->groupIDs) { if (!groups.contains(group)) groups << group; } } } return groups; } QStringList Satellites::getSatellites(const QString& group, Visibility vis) { QStringList result; foreach(const SatelliteP& sat, satellites) { if (sat->initialized) if ((group.isEmpty() || sat->groupIDs.contains(group)) && ! result.contains(sat->designation)) { if (vis==Both || (vis==Visible && sat->visible) || (vis==NotVisible && !sat->visible)) result << sat->designation; } } return result; } SatelliteP Satellites::getByID(const QString& id) { foreach(const SatelliteP& sat, satellites) { if (sat->initialized && sat->designation == id) return sat; } return SatelliteP(); } int Satellites::getSecondsToUpdate(void) { QDateTime nextUpdate = lastUpdate.addSecs(updateFrequencyHours * 3600); return QDateTime::currentDateTime().secsTo(nextUpdate); } void Satellites::setTleSources(QStringList tleSources) { updateUrls = tleSources; QSettings* conf = StelApp::getInstance().getSettings(); conf->beginGroup("Satellites"); // clear old source list QRegExp keyRE("^tle_url\\d+$"); foreach(QString key, conf->childKeys()) { if (keyRE.exactMatch(key)) conf->remove(key); } // set the new sources list int i=0; foreach (QString url, updateUrls) { conf->setValue(QString("tle_url%1").arg(i++), url); } conf->endGroup(); } bool Satellites::getFlagLabels(void) { return Satellite::showLabels; } void Satellites::setFlagLabels(bool b) { Satellite::showLabels = b; } void Satellites::checkForUpdate(void) { if (updatesEnabled && lastUpdate.addSecs(updateFrequencyHours * 3600) <= QDateTime::currentDateTime()) updateTLEs(); } void Satellites::updateTLEs(void) { if (updateState==Satellites::Updating) { qWarning() << "Satellites::updateTLEs already updating... will not start again current update is complete"; return; } else { qDebug() << "Satellites::updateTLEs starting update"; } lastUpdate = QDateTime::currentDateTime(); QSettings* conf = StelApp::getInstance().getSettings(); conf->setValue("Satellites/last_update", lastUpdate.toString("yyyy-MM-dd HH:mm:ss")); if (updateUrls.size() == 0) { qWarning() << "Satellites::updateTLEs no update URLs are defined... nothing to do"; emit(TleUpdateComplete(0)); return; } updateState = Satellites::Updating; emit(updateStateChanged(updateState)); updateFiles.clear(); numberDownloadsComplete = 0; if (progressBar==NULL) progressBar = StelApp::getInstance().getGui()->addProgressBar(); progressBar->setValue(0); progressBar->setMaximum(updateUrls.size()); progressBar->setVisible(true); progressBar->setFormat("TLE download %v/%m"); // set off the downloads for(int i=0; iget(QNetworkRequest(QUrl(updateUrls.at(i)))); } } void Satellites::updateDownloadComplete(QNetworkReply* reply) { // check the download worked, and save the data to file if this is the case. if (reply->error() != QNetworkReply::NoError) { qWarning() << "Satellites::updateDownloadComplete FAILED to download" << reply->url() << " Error: " << reply->errorString(); } else { // download completed successfully. try { QString partialName = QString("tle%1.txt").arg(numberDownloadsComplete); QString tleTmpFilePath = StelFileMgr::findFile("modules/Satellites", StelFileMgr::Flags(StelFileMgr::Writable|StelFileMgr::Directory)) + "/" + partialName; QFile tmpFile(tleTmpFilePath); tmpFile.remove(); tmpFile.open(QIODevice::WriteOnly | QIODevice::Text); tmpFile.write(reply->readAll()); tmpFile.close(); updateFiles << tleTmpFilePath; } catch (std::runtime_error &e) { qWarning() << "Satellites::updateDownloadComplete: cannot write TLE data to file:" << e.what(); } } numberDownloadsComplete++; if (progressBar) progressBar->setValue(numberDownloadsComplete); // all downloads are complete... do the update. if (numberDownloadsComplete >= updateUrls.size()) { updateFromFiles(); } } void Satellites::observerLocationChanged(StelLocation loc) { foreach(const SatelliteP& sat, satellites) { if (sat->initialized && sat->visible) sat->setObserverLocation(&loc); } } void Satellites::updateFromFiles(void) { // define a map of new TLE data - the key is the satellite designation QMap< QString, QPair > newTLE; if (progressBar) { progressBar->setValue(0); progressBar->setMaximum(updateFiles.size() + 1); progressBar->setFormat("TLE updating %v/%m"); } foreach(QString tleFilePath, updateFiles) { QFile tleFile(tleFilePath); if (tleFile.open(QIODevice::ReadOnly|QIODevice::Text)) { int lineNumber = 0; QString thisSatId(""); QPair tleLines; while (!tleFile.atEnd()) { QString line = QString(tleFile.readLine()).trimmed(); if (line.length() < 65) // this is a new designation { if (thisSatId!="" && !tleLines.first.isEmpty() && !tleLines.second.isEmpty()) { newTLE[thisSatId] = tleLines; } thisSatId = line; thisSatId.replace(QRegExp("\\[[^\\]]*\\]\\s*$"),""); // remove things in square brackets tleLines.first = QString(); tleLines.second = QString(); } else { if (QRegExp("^1 .*").exactMatch(line)) tleLines.first = line; else if (QRegExp("^2 .*").exactMatch(line)) tleLines.second = line; else qDebug() << "Satellites::updateFromFiles unprocessed line " << lineNumber << " in file " << tleFilePath; } } if (thisSatId!="" && !tleLines.first.isEmpty() && !tleLines.second.isEmpty()) { newTLE[thisSatId] = tleLines; } tleFile.close(); if (progressBar) progressBar->setValue(progressBar->value() + 1); } tleFile.remove(); // clean up downloaded TLE files } QFile satelliteJsonFile(satellitesJsonPath); StelJsonParser parser; QVariantMap map; if (!satelliteJsonFile.open(QIODevice::ReadOnly)) { qWarning() << "Satellites::updateFromFiles cannot open for reading " << satellitesJsonPath; } else { map = parser.parse(&satelliteJsonFile).toMap(); satelliteJsonFile.close(); } // Right, we should now have a map of all the elements we downloaded. For each satellite // which this module is managing, see if it exists with an updated element, and update it if so... int numUpdated = 0; QVariantList defCol; defCol << (double)defaultHintColor[0] << (double)defaultHintColor[1] << (double)defaultHintColor[2]; map["creator"] = "Satellites plugin (update)"; map["hintColor"] = defCol; map["shortName"] = "satellite orbital data"; QVariantMap sats = map["satellites"].toMap(); foreach(const SatelliteP& sat, satellites) { if (newTLE.contains(sat->designation)) { if (sat->elements[1] != newTLE[sat->designation].first || sat->elements[2] != newTLE[sat->designation].second) { QVariantMap satMap = sat->getMap(); satMap.remove("designation"); if (satMap["hintColor"].toList() == defCol) satMap.remove("hintColor"); // We have updated TLE elements for this satellite // update the satellites.json file satMap["tle1"] = newTLE[sat->designation].first; satMap["tle2"] = newTLE[sat->designation].second; strncpy(sat->elements[1], qPrintable(newTLE[sat->designation].first), 80); strncpy(sat->elements[2], qPrintable(newTLE[sat->designation].second), 80); numUpdated++; qDebug() << "updated orbital elements for: " << sat->designation; sats[sat->designation] = satMap; } } } map["satellites"] = sats; if (numUpdated>0) { satelliteJsonFile.remove(); if (!satelliteJsonFile.open(QIODevice::WriteOnly)) { qWarning() << "Satellites::updateFromFiles cannot open for writing " << satellitesJsonPath; } else { qDebug() << "Satellites::updateFromFiles Writing updated JSON file"; parser.write(map, &satelliteJsonFile); satelliteJsonFile.close(); } } delete progressBar; progressBar = NULL; qDebug() << "Satellites::updateFromFiles updated orbital elements for " << numUpdated << " satellites"; if (numUpdated==0) updateState = CompleteNoUpdates; else updateState = CompleteUpdates; emit(updateStateChanged(updateState)); emit(TleUpdateComplete(numUpdated)); } void Satellites::update(double deltaTime) { if (StelApp::getInstance().getCore()->getNavigator()->getCurrentLocation().planetName != earth->getEnglishName() || (!hintFader && hintFader.getInterstate() <= 0.)) return; hintFader.update((int)(deltaTime*1000)); foreach(const SatelliteP& sat, satellites) { if (sat->initialized && sat->visible) sat->update(deltaTime); } } void Satellites::draw(StelCore* core) { if (core->getNavigator()->getCurrentLocation().planetName != earth->getEnglishName() || (!hintFader && hintFader.getInterstate() <= 0.)) return; StelProjectorP prj = core->getProjection(StelCore::FrameAltAz); StelPainter painter(prj); painter.setFont(labelFont); Satellite::hintBrightness = hintFader.getInterstate(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glEnable(GL_TEXTURE_2D); Satellite::hintTexture->bind(); foreach (const SatelliteP& sat, satellites) { if (sat && sat->initialized && sat->visible) sat->draw(core, painter, 1.0); } if (GETSTELMODULE(StelObjectMgr)->getFlagSelectedObjectPointer()) drawPointer(core, painter); } void Satellites::drawPointer(StelCore* core, StelPainter& painter) { const StelNavigator* nav = core->getNavigator(); const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000); const QList newSelected = GETSTELMODULE(StelObjectMgr)->getSelectedObject("Satellite"); if (!newSelected.empty()) { const StelObjectP obj = newSelected[0]; Vec3d pos=obj->getJ2000EquatorialPos(nav); Vec3d screenpos; // Compute 2D pos and return if outside screen if (!prj->project(pos, screenpos)) return; glColor3f(0.4f,0.5f,0.8f); texPointer->bind(); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Normal transparency mode // Size on screen float size = obj->getAngularSize(core)*M_PI/180.*prj->getPixelPerRadAtCenter(); size += 12.f + 3.f*std::sin(2.f * StelApp::getInstance().getTotalRunTime()); // size+=20.f + 10.f*std::sin(2.f * StelApp::getInstance().getTotalRunTime()); painter.drawSprite2dMode(screenpos[0]-size/2, screenpos[1]-size/2, 20, 90); painter.drawSprite2dMode(screenpos[0]-size/2, screenpos[1]+size/2, 20, 0); painter.drawSprite2dMode(screenpos[0]+size/2, screenpos[1]+size/2, 20, -90); painter.drawSprite2dMode(screenpos[0]+size/2, screenpos[1]-size/2, 20, -180); } }