| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Data Visualization module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| /* |
| * Galaxy creation code obtained from http://beltoforion.de/galaxy/galaxy_en.html |
| * Thanks to Ingo Berg, great work. |
| * Licensed under a Creative Commons Attribution 3.0 License |
| * http://creativecommons.org/licenses/by/3.0/ |
| */ |
| |
| #include "galaxydata.h" |
| #include "cumulativedistributor.h" |
| #include "star.h" |
| #include <QtDataVisualization/qscatterdataproxy.h> |
| #include <QtDataVisualization/qvalue3daxis.h> |
| #include <QtDataVisualization/q3dscene.h> |
| #include <QtDataVisualization/q3dcamera.h> |
| #include <QtDataVisualization/qscatter3dseries.h> |
| #include <QtDataVisualization/q3dtheme.h> |
| #include <QtCore/qmath.h> |
| #include <QtCore/qrandom.h> |
| #include <QPainter> |
| |
| #include <QDebug> |
| |
| using namespace QtDataVisualization; |
| |
| static const int numOfStars = 70000; |
| static const int numOfDust = numOfStars / 2; |
| static const int numOfH2 = 200; |
| |
| GalaxyData::GalaxyData(Q3DScatter *scatter, |
| qreal rad, |
| qreal radCore, |
| qreal deltaAng, |
| qreal ex1, |
| qreal ex2) |
| : m_graph(scatter), |
| m_pStars(0), |
| m_pDust(0), |
| m_pH2(0), |
| m_radGalaxy(rad), |
| m_radCore(radCore), |
| m_angleOffset(deltaAng), |
| m_elEx1(ex1), |
| m_elEx2(ex2), |
| m_radFarField(m_radGalaxy * 2), |
| m_filtered(false), |
| m_minx(9999.9), |
| m_maxx(-9999.0), |
| m_miny(9999.9), |
| m_maxy(-9999.0) |
| { |
| m_graph->activeTheme()->setType(Q3DTheme::ThemeEbony); |
| m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow); |
| |
| m_graph->axisX()->setRange(-25000.0f, 25000.0f); |
| m_graph->axisZ()->setRange(-25000.0f, 25000.0f); |
| m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic); |
| m_graph->activeTheme()->setBackgroundColor(QColor(31, 31, 31)); |
| m_graph->activeTheme()->setWindowColor(QColor(31, 31, 31)); |
| m_graph->activeTheme()->setLabelBackgroundColor(QColor(31, 31, 31)); |
| m_graph->activeTheme()->setLabelTextColor(QColor(31, 31, 31)); |
| |
| QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, |
| this, &GalaxyData::handleFpsChange); |
| m_graph->setMeasureFps(true); |
| |
| createSeries(); |
| createGalaxy(); |
| } |
| |
| GalaxyData::~GalaxyData() |
| { |
| delete m_graph; |
| if (m_pStars) |
| delete [] m_pStars; |
| if (m_pDust) |
| delete [] m_pDust; |
| if (m_pH2) |
| delete [] m_pH2; |
| } |
| |
| void GalaxyData::createGalaxy() |
| { |
| if (m_pStars) |
| delete [] m_pStars; |
| m_pStars = new Star[numOfStars]; |
| |
| if (m_pDust) |
| delete [] m_pDust; |
| m_pDust = new Star[numOfDust]; |
| |
| if (m_pH2) |
| delete [] m_pH2; |
| m_pH2 = new Star[numOfH2 * 2]; |
| |
| |
| m_minx = 9999.9; |
| m_maxx = -9999.0; |
| m_miny = 9999.9; |
| m_maxy = -9999.0; |
| |
| // First star is the black hole at the center |
| m_pStars[0].m_a = 0; |
| m_pStars[0].m_b = 0; |
| m_pStars[0].m_angle = 0; |
| m_pStars[0].m_theta = 0; |
| m_pStars[0].m_center = QVector2D(0.0f, 0.0f); |
| m_pStars[0].calcXY(); |
| |
| // second star is at the edge of the core area |
| m_pStars[1].m_a = m_radCore; |
| m_pStars[1].m_b = m_radCore * getExcentricity(m_radCore); |
| m_pStars[1].m_angle = getAngularOffset(m_radCore); |
| m_pStars[1].m_theta = 0; |
| m_pStars[1].m_center = QVector2D(0.0f, 0.0f); |
| m_pStars[1].calcXY(); |
| checkMinMax(m_pStars[1]); |
| |
| // third star is at the edge of the disk |
| m_pStars[2].m_a = m_radGalaxy; |
| m_pStars[2].m_b = m_radGalaxy * getExcentricity(m_radGalaxy); |
| m_pStars[2].m_angle = getAngularOffset(m_radGalaxy); |
| m_pStars[2].m_theta = 0; |
| m_pStars[2].m_center = QVector2D(0.0f, 0.0f); |
| m_pStars[2].calcXY(); |
| checkMinMax(m_pStars[2]); |
| |
| CumulativeDistributor cd; |
| cd.setupRealistic(1.0, // Maximalintensität |
| 0.02, // k (bulge) |
| m_radGalaxy/3.0, // disc skalenlänge |
| m_radCore, // bulge radius |
| 0, // start der intensitätskurve |
| m_radFarField, // ende der intensitätskurve |
| 1000.0); // Anzahl der stützstellen |
| |
| for (int i = 3; i < numOfStars; ++i) { |
| qreal rad = cd.valFromProp(QRandomGenerator::global()->generateDouble()); |
| |
| m_pStars[i].m_a = rad; |
| m_pStars[i].m_b = rad * getExcentricity(rad); |
| m_pStars[i].m_angle = getAngularOffset(rad); |
| m_pStars[i].m_theta = 360.0 * QRandomGenerator::global()->generateDouble(); |
| m_pStars[i].m_center = QVector2D(0.0f, 0.0f); |
| m_pStars[i].calcXY(); |
| |
| checkMinMax(m_pStars[i]); |
| } |
| |
| // Initialize Dust |
| qreal x, y, rad; |
| for (int i = 0; i < numOfDust; ++i) |
| { |
| x = 2.0 * m_radGalaxy * QRandomGenerator::global()->generateDouble() - m_radGalaxy; |
| y = 2.0 * m_radGalaxy * QRandomGenerator::global()->generateDouble() - m_radGalaxy; |
| rad = sqrt(x*x + y*y); |
| |
| m_pDust[i].m_a = rad; |
| m_pDust[i].m_b = rad * getExcentricity(rad); |
| m_pDust[i].m_angle = getAngularOffset(rad); |
| m_pDust[i].m_theta = 360.0 * QRandomGenerator::global()->generateDouble(); |
| m_pDust[i].m_center = QVector2D(0.0f, 0.0f); |
| m_pDust[i].calcXY(); |
| |
| checkMinMax(m_pDust[i]); |
| } |
| |
| // Initialize H2 |
| for (int i = 0; i < numOfH2; ++i) |
| { |
| x = 2*(m_radGalaxy) * QRandomGenerator::global()->generateDouble() - (m_radGalaxy); |
| y = 2*(m_radGalaxy) * QRandomGenerator::global()->generateDouble() - (m_radGalaxy); |
| rad = sqrt(x*x + y*y); |
| |
| int k1 = 2*i; |
| m_pH2[k1].m_a = rad; |
| m_pH2[k1].m_b = rad * getExcentricity(rad); |
| m_pH2[k1].m_angle = getAngularOffset(rad); |
| m_pH2[k1].m_theta = 360.0 * QRandomGenerator::global()->generateDouble(); |
| m_pH2[k1].m_center = QVector2D(0.0f, 0.0f); |
| m_pH2[k1].calcXY(); |
| |
| int k2 = 2*i + 1; |
| m_pH2[k2].m_a = rad + 1000.0; |
| m_pH2[k2].m_b = rad * getExcentricity(rad); |
| m_pH2[k2].m_angle = m_pH2[k1].m_angle; |
| m_pH2[k2].m_theta = m_pH2[k1].m_theta; |
| m_pH2[k2].m_center = m_pH2[k1].m_center; |
| m_pH2[k2].calcXY(); |
| } |
| |
| qreal max = qMax(m_maxx, m_maxy); |
| qreal min = -qMin(m_minx, m_miny); |
| max = qMax(min, max); |
| m_range = int((max + 500.0) / 1000.0) * 1000; |
| m_graph->axisX()->setRange(-float(m_range), float(m_range)); |
| m_graph->axisZ()->setRange(-float(m_range), float(m_range)); |
| |
| if (!m_filtered) |
| createNormalDataView(); |
| else |
| createFilteredView(); |
| } |
| |
| void GalaxyData::createSeries() |
| { |
| QScatterDataProxy *proxyNormal = new QScatterDataProxy; |
| m_normalSeries = new QScatter3DSeries(proxyNormal); |
| m_normalSeries->setMesh(QAbstract3DSeries::MeshPoint); |
| m_graph->addSeries(m_normalSeries); |
| |
| QScatterDataProxy *proxyDust = new QScatterDataProxy; |
| m_dustSeries = new QScatter3DSeries(proxyDust); |
| m_dustSeries->setMesh(QAbstract3DSeries::MeshPoint); |
| m_graph->addSeries(m_dustSeries); |
| |
| QScatterDataProxy *proxyH2 = new QScatterDataProxy; |
| m_H2Series = new QScatter3DSeries(proxyH2); |
| m_H2Series->setMesh(QAbstract3DSeries::MeshPoint); |
| m_graph->addSeries(m_H2Series); |
| |
| QScatterDataProxy *proxyFiltered = new QScatterDataProxy; |
| m_filteredSeries = new QScatter3DSeries(proxyFiltered); |
| m_filteredSeries->setMesh(QAbstract3DSeries::MeshCube); |
| m_graph->addSeries(m_filteredSeries); |
| |
| } |
| |
| void GalaxyData::createNormalDataView() |
| { |
| QScatterDataArray *dataArray = new QScatterDataArray; |
| dataArray->resize(numOfStars); |
| QScatterDataItem *ptrToDataArray = &dataArray->first(); |
| |
| for (uint i = 0; i < numOfStars; i++) { |
| ptrToDataArray->setPosition(QVector3D(m_pStars[i].m_pos.x(), |
| 0.0f, |
| m_pStars[i].m_pos.y())); |
| ptrToDataArray++; |
| } |
| |
| m_normalSeries->dataProxy()->resetArray(dataArray); |
| m_normalSeries->setMesh(QAbstract3DSeries::MeshPoint); |
| m_normalSeries->setBaseColor(Qt::white); |
| |
| dataArray = new QScatterDataArray; |
| dataArray->resize(numOfDust); |
| ptrToDataArray = &dataArray->first(); |
| |
| for (uint i = 0; i < numOfDust; i++) { |
| ptrToDataArray->setPosition(QVector3D(m_pDust[i].m_pos.x(), |
| 0.0f, |
| m_pDust[i].m_pos.y())); |
| ptrToDataArray++; |
| } |
| |
| m_dustSeries->dataProxy()->resetArray(dataArray); |
| m_dustSeries->setMesh(QAbstract3DSeries::MeshPoint); |
| m_dustSeries->setBaseColor(QColor(131, 111, 255)); |
| |
| dataArray = new QScatterDataArray; |
| dataArray->resize(numOfDust); |
| ptrToDataArray = &dataArray->first(); |
| |
| uint H2Count = numOfH2 * 2; |
| for (uint i = 0; i < H2Count; i++) { |
| ptrToDataArray->setPosition(QVector3D(m_pH2[i].m_pos.x(), |
| 0.0f, |
| m_pH2[i].m_pos.y())); |
| ptrToDataArray++; |
| } |
| |
| m_H2Series->dataProxy()->resetArray(dataArray); |
| m_H2Series->setMesh(QAbstract3DSeries::MeshPoint); |
| m_H2Series->setBaseColor(Qt::red); |
| } |
| |
| void GalaxyData::createFilteredView() |
| { |
| int steps = (m_range / 1000) * 2; |
| int tableSize = steps * steps; |
| int *table = new int[tableSize]; |
| for (int i = 0; i < tableSize; i++) |
| table[i] = 0; |
| qreal add = qreal(m_range); |
| int max = 0; |
| |
| for (uint i = 0; i < numOfStars; i++) { |
| int x = int(m_pStars[i].m_pos.x() + add) / 1000; |
| int y = int(m_pStars[i].m_pos.y() + add) / 1000; |
| table[y * steps + x] = table[y * steps + x] + 1; |
| |
| if (max < table[y * steps + x]) |
| max = table[y * steps + x]; |
| } |
| |
| // Count how many cells have data |
| int nActiveCell = 0; |
| for (int i = 0; i < tableSize; i++) { |
| if (table[i]) |
| nActiveCell++; |
| } |
| |
| |
| QScatterDataArray *dataArray = new QScatterDataArray; |
| dataArray->resize(nActiveCell); |
| QScatterDataItem *ptrToDataArray = &dataArray->first(); |
| |
| for (int y = 0; y < steps; y++) { |
| for (int x = 0; x < steps; x++) { |
| if (table[y * steps + x]) { |
| ptrToDataArray->setPosition(QVector3D(float(x) * 1000.0f - add + 500.0f, |
| float(table[y * steps + x]), |
| float(y) * 1000.0f - add + 500.0f)); |
| ptrToDataArray++; |
| } |
| } |
| } |
| |
| m_filteredSeries->dataProxy()->resetArray(dataArray); |
| m_filteredSeries->setMesh(QAbstract3DSeries::MeshCube); |
| m_filteredSeries->setItemSize(0.1f); |
| |
| m_graph->axisY()->setRange(0.0f, float(max + 1)); |
| |
| qDebug() << "max = " << max; |
| } |
| |
| void GalaxyData::checkMinMax(const Star &star) |
| { |
| if (star.m_pos.x() < m_minx) |
| m_minx = star.m_pos.x(); |
| if (star.m_pos.x() > m_maxx) |
| m_maxx = star.m_pos.x(); |
| if (star.m_pos.y() < m_miny) |
| m_miny = star.m_pos.y(); |
| if (star.m_pos.y() > m_maxy) |
| m_maxy = star.m_pos.y(); |
| } |
| |
| qreal GalaxyData::getExcentricity(qreal r) const |
| { |
| if (r < m_radCore) |
| { |
| // Core region of the galaxy. Innermost part is round |
| // excentricity increasing linear to the border of the core. |
| return 1 + (r / m_radCore) * (m_elEx1-1); |
| } else if (r > m_radCore && r <= m_radGalaxy) { |
| return m_elEx1 + (r - m_radCore) / (m_radGalaxy - m_radCore) * (m_elEx2 - m_elEx1); |
| } else if (r > m_radGalaxy && r < m_radFarField) { |
| // excentricity is slowly reduced to 1. |
| return m_elEx2 + (r - m_radGalaxy) / (m_radFarField - m_radGalaxy) * (1 - m_elEx2); |
| } else { |
| return 1.0; |
| } |
| } |
| |
| qreal GalaxyData::getAngularOffset(qreal rad) const |
| { |
| return rad * m_angleOffset; |
| } |
| |
| void GalaxyData::radiusGalaxyChanged(int value) |
| { |
| m_radGalaxy = qreal(value); |
| createGalaxy(); |
| } |
| |
| void GalaxyData::radiusCoreChanged(int value) |
| { |
| m_radCore = qreal(value); |
| createGalaxy(); |
| } |
| |
| void GalaxyData::angleOffsetChanged(int value) |
| { |
| m_angleOffset = qreal(value) / 100000.0; |
| createGalaxy(); |
| } |
| |
| void GalaxyData::eccentricityInnerChanged(int value) |
| { |
| m_elEx1 = qreal(value) / 100.0; |
| createGalaxy(); |
| } |
| |
| void GalaxyData::eccentricityOuterChanged(int value) |
| { |
| m_elEx2 = qreal(value) / 100.0; |
| createGalaxy(); |
| } |
| |
| void GalaxyData::setFilteredEnabled(bool enabled) |
| { |
| m_filtered = enabled; |
| if (enabled) { |
| QLinearGradient gr(0, 0, 1, 100); |
| gr.setColorAt(0.0, Qt::white); |
| gr.setColorAt(0.04, Qt::green); |
| gr.setColorAt(0.1, Qt::darkGreen); |
| gr.setColorAt(1.0, Qt::red); |
| |
| m_filteredSeries->setBaseGradient(gr); |
| m_filteredSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient); |
| |
| m_normalSeries->setVisible(false); |
| |
| createFilteredView(); |
| |
| m_filteredSeries->setVisible(true); |
| } else { |
| m_normalSeries->setColorStyle(Q3DTheme::ColorStyleUniform); |
| m_graph->axisY()->setRange(-1.0f, 1.0f); |
| m_normalSeries->setItemSize(0.0f); |
| |
| m_filteredSeries->setVisible(false); |
| |
| createNormalDataView(); |
| |
| m_normalSeries->setVisible(true); |
| } |
| } |
| |
| |
| void GalaxyData::setStaticEnabled(bool enabled) |
| { |
| if (enabled) |
| m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic); |
| else |
| m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationDefault); |
| } |
| |
| void GalaxyData::setStarsVisible(bool enabled) |
| { |
| m_normalSeries->setVisible(enabled); |
| } |
| |
| void GalaxyData::setDustVisible(bool enabled) |
| { |
| m_dustSeries->setVisible(enabled); |
| } |
| |
| void GalaxyData::setH2Visible(bool enabled) |
| { |
| m_H2Series->setVisible(enabled); |
| } |
| |
| void GalaxyData::resetValues() |
| { |
| m_radiusGalaxySlider->setValue(15000); |
| m_radiusCoreSlider->setValue(4000); |
| m_angleOffsetSlider->setValue(40); |
| m_eccentricityInnerSlider->setValue(90); |
| m_eccentricityOuterSlider->setValue(90); |
| } |
| |
| void GalaxyData::handleFpsChange(qreal fps) |
| { |
| static const QString fpsPrefix(QStringLiteral("FPS: ")); |
| m_fpsLabel->setText(fpsPrefix + QString::number(qRound(fps))); |
| } |