a lot!!! of changes
added main menu added debug display many debug widgets for plotting live data worked on android live sensors added offline-data sensor feeding some dummy data sensors worked on the map display added ui debug for grid-points, particles and weights added a cool dude to display the estimation added real filtering based on the Indoor components c++11 fixes for android compilation online and offline filtering support new resampling technique for testing map loading via dialog
This commit is contained in:
90
ui/Icons.h
Normal file
90
ui/Icons.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef ICONS_H
|
||||
#define ICONS_H
|
||||
|
||||
#include "../misc/fixc11.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QRgb>
|
||||
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
class Icons {
|
||||
|
||||
public:
|
||||
|
||||
|
||||
static const QPixmap& getPixmap(const std::string& name, const int size = 32) {
|
||||
|
||||
// caching
|
||||
static std::unordered_map<std::string, QPixmap> cache;
|
||||
|
||||
// try to get the image from the cache
|
||||
const std::string cacheKey = std::to_string(size) + name;
|
||||
auto it = cache.find(cacheKey);
|
||||
|
||||
// not in cache?
|
||||
if (it == cache.end()) {
|
||||
|
||||
// build
|
||||
const QColor fill = Qt::transparent;
|
||||
const std::string file = "://res/icons/" + name + ".svg";
|
||||
QSvgRenderer renderer(QString(file.c_str()));
|
||||
QPixmap pm(size, size);
|
||||
pm.fill(fill);
|
||||
QPainter painter(&pm);
|
||||
renderer.render(&painter, pm.rect());
|
||||
|
||||
// add to cache
|
||||
cache[cacheKey] = pm;
|
||||
|
||||
}
|
||||
|
||||
// done
|
||||
return cache[cacheKey];
|
||||
|
||||
}
|
||||
|
||||
static const QPixmap& getPixmapColored(const std::string& name, const QColor color, const int size = 32) {
|
||||
|
||||
// caching
|
||||
static std::unordered_map<std::string, QPixmap> cache;
|
||||
|
||||
// try to get the image from the cache
|
||||
const QString hex = color.name();
|
||||
const std::string cacheKey = hex.toStdString() + "_" + std::to_string(size) + "_" + name;
|
||||
auto it = cache.find(cacheKey);
|
||||
|
||||
// not in cache?
|
||||
if (it == cache.end()) {
|
||||
|
||||
// copy
|
||||
QPixmap colored = getPixmap(name, size);
|
||||
QPainter painter(&colored);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(colored.rect(), color);
|
||||
painter.end();
|
||||
|
||||
// add to cache
|
||||
cache[cacheKey] = colored;
|
||||
|
||||
}
|
||||
|
||||
// done
|
||||
return cache[cacheKey];
|
||||
|
||||
}
|
||||
|
||||
static QIcon getIcon(const std::string& name, const int size = 32) {
|
||||
|
||||
return QIcon(getPixmap(name, size));
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // ICONS_H
|
||||
35
ui/MainWindow.cpp
Normal file
35
ui/MainWindow.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
|
||||
#include "map/MapView.h"
|
||||
#include "menu/MainMenu.h"
|
||||
#include "debug/SensorDataWidget.h"
|
||||
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
setMinimumHeight(500);
|
||||
setMinimumWidth(500);
|
||||
|
||||
mapView = new MapView(this);
|
||||
mainMenu = new MainMenu(this);
|
||||
sensorWidget = new SensorDataWidget(this);
|
||||
|
||||
//sensorWidget->setVisible(false);
|
||||
showMaximized();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::resizeEvent(QResizeEvent* event) {
|
||||
|
||||
const int w = event->size().width();
|
||||
const int h = event->size().height();
|
||||
|
||||
mapView->setGeometry(0,0,w,h);
|
||||
mainMenu->setGeometry(0,0,w,64);
|
||||
sensorWidget->setGeometry(0,64,w,h-64);
|
||||
|
||||
}
|
||||
|
||||
|
||||
43
ui/MainWindow.h
Normal file
43
ui/MainWindow.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class MapView;
|
||||
class MainMenu;
|
||||
class SensorDataWidget;
|
||||
|
||||
class MainWindow : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
|
||||
private:
|
||||
|
||||
MapView* mapView = nullptr;
|
||||
MainMenu* mainMenu = nullptr;
|
||||
SensorDataWidget* sensorWidget = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
MapView* getMapView() const {return mapView;}
|
||||
MainMenu* getMainMenu() const {return mainMenu;}
|
||||
SensorDataWidget* getSensorDataWidget() const {return sensorWidget;}
|
||||
|
||||
|
||||
// void setMapView(QWidget* widget) {mapView = widget; mapView->setParent(this);}
|
||||
// void setMainMenu(QWidget* widget) {mainMenu = widget; mainMenu->setParent(this);}
|
||||
// void setSensorWidget(QWidget* widget) {sensorWidget = widget; sensorWidget->setParent(this);}
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
49
ui/debug/PlotTurns.cpp
Normal file
49
ui/debug/PlotTurns.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "PlotTurns.h"
|
||||
#include <QPainter>
|
||||
|
||||
PlotTurns::PlotTurns(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
setMinimumWidth(96);
|
||||
setMinimumHeight(96);
|
||||
|
||||
resize(96, 96);
|
||||
|
||||
// setMaximumWidth(64);
|
||||
// setMaximumHeight(64);
|
||||
|
||||
}
|
||||
|
||||
void PlotTurns::add(const Timestamp ts, const TurnData& data) {
|
||||
(void) ts;
|
||||
this->data = data;
|
||||
static int i = 0;
|
||||
if (++i % 4 == 0) {
|
||||
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void PlotTurns::paintEvent(QPaintEvent* evt) {
|
||||
|
||||
(void) evt;
|
||||
QPainter p(this);
|
||||
|
||||
const float s = std::min(width(), height());
|
||||
const float s1 = s / 1.9;
|
||||
|
||||
const float cx = width() / 2;
|
||||
const float cy = height() / 2;
|
||||
|
||||
const float x1 = cx + std::cos(data.radSinceStart-M_PI_2) * s1;
|
||||
const float y1 = cy + std::sin(data.radSinceStart-M_PI_2) * s1;
|
||||
|
||||
p.fillRect(0,0,width(),height(),QColor(255,255,255,192));
|
||||
p.setPen(Qt::black);
|
||||
p.drawRect(0,0,width()-1,height()-1);
|
||||
|
||||
const QPen pen(Qt::black, 2);
|
||||
p.setPen(pen);
|
||||
p.drawLine(cx, cy, x1, y1);
|
||||
|
||||
p.end();
|
||||
|
||||
}
|
||||
30
ui/debug/PlotTurns.h
Normal file
30
ui/debug/PlotTurns.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef PLOTTURNS_H
|
||||
#define PLOTTURNS_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "../sensors/TurnSensor.h"
|
||||
#include <Indoor/data/Timestamp.h>
|
||||
|
||||
class PlotTurns : public QWidget {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
TurnData data;
|
||||
|
||||
public:
|
||||
|
||||
explicit PlotTurns(QWidget *parent = 0);
|
||||
|
||||
void add(const Timestamp ts, const TurnData& data);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
void paintEvent(QPaintEvent*);
|
||||
|
||||
};
|
||||
|
||||
#endif // PLOTTURNS_H
|
||||
53
ui/debug/PlotWiFiScan.cpp
Normal file
53
ui/debug/PlotWiFiScan.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "../misc/fixc11.h"
|
||||
#include "PlotWiFiScan.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStaticText>
|
||||
|
||||
PlotWiFiScan::PlotWiFiScan(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
setMinimumWidth(96);
|
||||
setMinimumHeight(96);
|
||||
|
||||
//setAutoFillBackground(false);
|
||||
|
||||
}
|
||||
|
||||
void PlotWiFiScan::add(const Timestamp ts, const WiFiMeasurements& data) {
|
||||
(void) ts;
|
||||
this->data = data;
|
||||
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void PlotWiFiScan::paintEvent(QPaintEvent* evt) {
|
||||
|
||||
(void) evt;
|
||||
QPainter p(this);
|
||||
|
||||
const int x0 = 4; const int xw = 150;
|
||||
const int y0 = 3;
|
||||
const int lh = 13;
|
||||
|
||||
int x = x0;
|
||||
int y = y0;
|
||||
|
||||
|
||||
p.fillRect(0,0,width(),height(),QColor(255,255,255,192));
|
||||
p.setPen(Qt::black);
|
||||
p.drawRect(0,0,width()-1,height()-1);
|
||||
|
||||
const QFont font("Arial", 9);
|
||||
p.setFont(font);
|
||||
p.setPen(Qt::black);
|
||||
|
||||
for (const WiFiMeasurement& m : data.entries) {
|
||||
const std::string& mac = m.getAP().getMAC().asString();
|
||||
std::string str = mac + ": " + std::to_string((int)m.getRSSI());
|
||||
p.drawStaticText(x, y, QStaticText(str.c_str()));
|
||||
y += lh;
|
||||
if (y > 90) {y = y0; x += xw;}
|
||||
}
|
||||
|
||||
p.end();
|
||||
|
||||
}
|
||||
30
ui/debug/PlotWiFiScan.h
Normal file
30
ui/debug/PlotWiFiScan.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef PLOTWIFISCAN_H
|
||||
#define PLOTWIFISCAN_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "../sensors/WiFiSensor.h"
|
||||
|
||||
class PlotWiFiScan : public QWidget {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
WiFiMeasurements data;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
explicit PlotWiFiScan(QWidget *parent = 0);
|
||||
|
||||
void add(const Timestamp ts, const WiFiMeasurements& data);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
void paintEvent(QPaintEvent*);
|
||||
|
||||
};
|
||||
|
||||
#endif // PLOTWIFISCAN_H
|
||||
217
ui/debug/SensorDataWidget.cpp
Normal file
217
ui/debug/SensorDataWidget.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "../misc/fixc11.h"
|
||||
#include "SensorDataWidget.h"
|
||||
|
||||
#include "plot/PlottWidget.h"
|
||||
#include <QGridLayout>
|
||||
#include <QColor>
|
||||
|
||||
#include "../sensors/SensorFactory.h"
|
||||
#include "PlotTurns.h"
|
||||
#include "PlotWiFiScan.h"
|
||||
|
||||
|
||||
template <typename Data> void removeOld(Data& data, const Timestamp limit) {
|
||||
if (data.size() < 2) {return;}
|
||||
while ( (data.back().key - data.front().key) > limit.ms()) {
|
||||
data.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
template <int num> class PlotXLines : public PlotWidget {
|
||||
|
||||
protected:
|
||||
|
||||
QColor colors[4] = {QColor(255,0,0), QColor(0,192,0), QColor(0,0,255), QColor(0,0,0)};
|
||||
LinePlot line[num];
|
||||
|
||||
public:
|
||||
|
||||
PlotXLines(QWidget* parent) : PlotWidget(parent) {
|
||||
for (int i = 0; i < num; ++i) {
|
||||
pc.addPlot(&line[i]);
|
||||
line[i].setColor(colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void addLineNode(const Timestamp ts, const float y, const int idx) {
|
||||
LinePlot& lp = line[idx];
|
||||
lp.getData().add(ts.ms(), y);
|
||||
}
|
||||
|
||||
Timestamp lastRefresh;
|
||||
bool needsRefresh(const Timestamp ts) {
|
||||
const Timestamp diff = ts - lastRefresh;
|
||||
return (diff > Timestamp::fromMS(100));
|
||||
}
|
||||
|
||||
|
||||
void refresh(const Timestamp ts) {
|
||||
|
||||
// ensure event from main-thread using queued-connection
|
||||
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
||||
|
||||
lastRefresh = ts;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PlotAcc : public PlotXLines<3> {
|
||||
|
||||
protected:
|
||||
|
||||
PointPlot steps;
|
||||
|
||||
public:
|
||||
|
||||
PlotAcc(QWidget* parent) : PlotXLines(parent) {
|
||||
steps.setColor(colors[2]);
|
||||
steps.setPointSize(8);
|
||||
pc.addPlot(&steps);
|
||||
const float s = 4.2;
|
||||
const float ref = 9.81;
|
||||
pc.setValRange(Range(ref-s, ref+s));
|
||||
}
|
||||
|
||||
void addStep(const Timestamp ts) {
|
||||
steps.getData().add(ts.ms(), 9.81);
|
||||
}
|
||||
|
||||
void add(const Timestamp ts, const AccelerometerData& data) {
|
||||
addLineNode(ts, data.x, 0);
|
||||
addLineNode(ts, data.y, 1);
|
||||
addLineNode(ts, data.z, 2);
|
||||
if (needsRefresh(ts)) {
|
||||
limit();
|
||||
refresh(ts);
|
||||
}
|
||||
}
|
||||
|
||||
void limit() {
|
||||
const Timestamp limit = Timestamp::fromMS(3000);
|
||||
removeOld(line[0].getData(), limit);
|
||||
removeOld(line[1].getData(), limit);
|
||||
removeOld(line[2].getData(), limit);
|
||||
removeOld(steps.getData(), limit - Timestamp::fromMS(100)); // remove steps a little before. prevents errors
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PlotGyro : public PlotXLines<3> {
|
||||
|
||||
public:
|
||||
|
||||
PlotGyro(QWidget* parent) : PlotXLines(parent) {
|
||||
const float s = 1;
|
||||
const float ref = 0;
|
||||
pc.setValRange(Range(ref-s, ref+s));
|
||||
}
|
||||
|
||||
void add(const Timestamp ts, const GyroscopeData& data) {
|
||||
addLineNode(ts, data.x, 0);
|
||||
addLineNode(ts, data.y, 1);
|
||||
addLineNode(ts, data.z, 2);
|
||||
if (needsRefresh(ts)) {
|
||||
limit();
|
||||
refresh(ts);
|
||||
}
|
||||
}
|
||||
|
||||
void limit() {
|
||||
const Timestamp limit = Timestamp::fromMS(3000);
|
||||
removeOld(line[0].getData(), limit);
|
||||
removeOld(line[1].getData(), limit);
|
||||
removeOld(line[2].getData(), limit);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PlotBaro : public PlotXLines<1> {
|
||||
|
||||
public:
|
||||
|
||||
PlotBaro(QWidget* parent) : PlotXLines(parent) {
|
||||
|
||||
}
|
||||
|
||||
void add(const Timestamp ts, const BarometerData& data) {
|
||||
addLineNode(ts, data.hPa, 0);
|
||||
if (needsRefresh(ts)) {
|
||||
limit();
|
||||
refresh(ts);
|
||||
}
|
||||
const float s = 0.5;
|
||||
const float ref = line[0].getData().front().val;
|
||||
pc.setValRange(Range(ref-s, ref+s));
|
||||
}
|
||||
|
||||
void limit() {
|
||||
removeOld(line[0].getData(), Timestamp::fromMS(8000));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PlotTurn : public QWidget {
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
SensorDataWidget::SensorDataWidget(QWidget* parent) : QWidget(parent) {
|
||||
|
||||
QGridLayout* lay = new QGridLayout(this);
|
||||
|
||||
plotGyro = new PlotGyro(this);
|
||||
plotAcc = new PlotAcc(this);
|
||||
plotBaro = new PlotBaro(this);
|
||||
plotTurn = new PlotTurns(this);
|
||||
plotWiFi = new PlotWiFiScan(this);
|
||||
|
||||
lay->addWidget(plotGyro, 0, 0, 1, 4, Qt::AlignTop);
|
||||
lay->addWidget(plotAcc, 1, 0, 1, 4, Qt::AlignTop);
|
||||
lay->addWidget(plotBaro, 2, 0, 1, 4, Qt::AlignTop);
|
||||
lay->addWidget(plotTurn, 3, 0, 1, 1, Qt::AlignTop);
|
||||
lay->addWidget(plotWiFi, 3, 1, 1, 3, Qt::AlignTop);
|
||||
|
||||
SensorFactory::get().getAccelerometer().addListener(this);
|
||||
SensorFactory::get().getGyroscope().addListener(this);
|
||||
SensorFactory::get().getBarometer().addListener(this);
|
||||
SensorFactory::get().getSteps().addListener(this);
|
||||
SensorFactory::get().getTurns().addListener(this);
|
||||
SensorFactory::get().getWiFi().addListener(this);
|
||||
|
||||
//setAutoFillBackground(false);
|
||||
|
||||
}
|
||||
|
||||
void SensorDataWidget::onSensorData(Sensor<AccelerometerData>* sensor, const Timestamp ts, const AccelerometerData& data) {
|
||||
(void) sensor;
|
||||
((PlotAcc*)plotAcc)->add(ts, data);
|
||||
}
|
||||
|
||||
void SensorDataWidget::onSensorData(Sensor<StepData>* sensor, const Timestamp ts, const StepData& data) {
|
||||
(void) sensor;
|
||||
(void) data;
|
||||
((PlotAcc*)plotAcc)->addStep(ts);
|
||||
}
|
||||
|
||||
void SensorDataWidget::onSensorData(Sensor<GyroscopeData>* sensor, const Timestamp ts, const GyroscopeData& data) {
|
||||
(void) sensor;
|
||||
((PlotGyro*)plotGyro)->add(ts, data);
|
||||
}
|
||||
|
||||
void SensorDataWidget::onSensorData(Sensor<BarometerData>* sensor, const Timestamp ts, const BarometerData& data) {
|
||||
(void) sensor;
|
||||
((PlotBaro*)plotBaro)->add(ts, data);
|
||||
}
|
||||
|
||||
void SensorDataWidget::onSensorData(Sensor<TurnData>* sensor, const Timestamp ts, const TurnData& data) {
|
||||
(void) sensor;
|
||||
((PlotTurns*)plotTurn)->add(ts, data);
|
||||
}
|
||||
|
||||
void SensorDataWidget::onSensorData(Sensor<WiFiMeasurements>* sensor, const Timestamp ts, const WiFiMeasurements& data) {
|
||||
(void) sensor;
|
||||
((PlotWiFiScan*)plotWiFi)->add(ts, data);
|
||||
}
|
||||
|
||||
53
ui/debug/SensorDataWidget.h
Normal file
53
ui/debug/SensorDataWidget.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef SENSORDATAWIDGET_H
|
||||
#define SENSORDATAWIDGET_H
|
||||
|
||||
#include "../misc/fixc11.h"
|
||||
#include "plot/PlottWidget.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "../sensors/AccelerometerSensor.h"
|
||||
#include "../sensors/GyroscopeSensor.h"
|
||||
#include "../sensors/BarometerSensor.h"
|
||||
#include "../sensors/StepSensor.h"
|
||||
#include "../sensors/TurnSensor.h"
|
||||
#include "../sensors/WiFiSensor.h"
|
||||
|
||||
class PlotWidget;
|
||||
|
||||
/** debug display for sensor data */
|
||||
class SensorDataWidget :
|
||||
public QWidget,
|
||||
public SensorListener<AccelerometerData>,
|
||||
public SensorListener<GyroscopeData>,
|
||||
public SensorListener<BarometerData>,
|
||||
public SensorListener<StepData>,
|
||||
public SensorListener<TurnData>,
|
||||
public SensorListener<WiFiMeasurements> {
|
||||
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
SensorDataWidget(QWidget* parent);
|
||||
|
||||
void onSensorData(Sensor<AccelerometerData>* sensor, const Timestamp ts, const AccelerometerData& data) override;
|
||||
void onSensorData(Sensor<GyroscopeData>* sensor, const Timestamp ts, const GyroscopeData& data) override;
|
||||
void onSensorData(Sensor<BarometerData>* sensor, const Timestamp ts, const BarometerData& data) override;
|
||||
void onSensorData(Sensor<StepData>* sensor, const Timestamp ts, const StepData& data) override;
|
||||
void onSensorData(Sensor<TurnData>* sensor, const Timestamp ts, const TurnData& data) override;
|
||||
void onSensorData(Sensor<WiFiMeasurements>* sensor, const Timestamp ts, const WiFiMeasurements& data) override;
|
||||
|
||||
private:
|
||||
|
||||
PlotWidget* plotGyro;
|
||||
PlotWidget* plotAcc;
|
||||
PlotWidget* plotBaro;
|
||||
QWidget* plotTurn;
|
||||
QWidget* plotWiFi;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // SENSORDATAWIDGET_H
|
||||
66
ui/debug/plot/Axes.h
Normal file
66
ui/debug/plot/Axes.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#ifndef AXES_H
|
||||
#define AXES_H
|
||||
|
||||
#include "Range.h"
|
||||
|
||||
class Axes {
|
||||
|
||||
/** min/max value to display */
|
||||
Range range;
|
||||
|
||||
/** number of available pixels for above range */
|
||||
int pixels;
|
||||
|
||||
/** whether to invert the axes */
|
||||
bool invert = false;
|
||||
|
||||
public:
|
||||
|
||||
void setMin(const float min) {this->range.min = min;}
|
||||
float getMin() const {return this->range.min;}
|
||||
|
||||
void setMax(const float max) {this->range.max = max;}
|
||||
float getMax() const {return this->range.max;}
|
||||
|
||||
void setRange(const Range& range) {this->range = range;}
|
||||
const Range& getRange() const {return this->range;}
|
||||
|
||||
void setPixels(const int px) {this->pixels = px;}
|
||||
int getPixels() const {return this->pixels;}
|
||||
|
||||
void setInverted(const bool inverted) {this->invert = inverted;}
|
||||
bool isInverted() const {return this->invert;}
|
||||
|
||||
float convert(const float val) const {
|
||||
float percent = (val - range.min) / (range.getSize());
|
||||
if (invert) {percent = 1-percent;}
|
||||
return percent * pixels;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class AxesX : public Axes {
|
||||
|
||||
public:
|
||||
|
||||
AxesX() {
|
||||
setInverted(false);
|
||||
}
|
||||
|
||||
void setWidth(const int px) {setPixels(px);}
|
||||
|
||||
};
|
||||
|
||||
class AxesY : public Axes {
|
||||
|
||||
public:
|
||||
|
||||
AxesY() {
|
||||
setInverted(true);
|
||||
}
|
||||
|
||||
void setHeight(const int px) {setPixels(px);}
|
||||
|
||||
};
|
||||
|
||||
#endif // AXES_H
|
||||
80
ui/debug/plot/Data.h
Normal file
80
ui/debug/plot/Data.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef PLOT_DATA_H
|
||||
#define PLOT_DATA_H
|
||||
|
||||
#include <vector>
|
||||
#include "Range.h"
|
||||
#include <cmath>
|
||||
|
||||
class Data {
|
||||
|
||||
using Key = float;
|
||||
using Value = float;
|
||||
|
||||
public:
|
||||
|
||||
struct KeyVal {
|
||||
Key key;
|
||||
Value val;
|
||||
KeyVal(const Key& key, const Value& val) : key(key), val(val) {;}
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
/** contained data */
|
||||
std::vector<KeyVal> data;
|
||||
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** add a new value */
|
||||
void add(const Key key, const Value val) {
|
||||
data.push_back(KeyVal(key,val));
|
||||
}
|
||||
|
||||
/** remove the given index */
|
||||
void remove(const int idx) {
|
||||
data.erase(data.begin()+idx);
|
||||
}
|
||||
|
||||
Key getKey(const int idx) const {
|
||||
return data[idx].key;
|
||||
}
|
||||
|
||||
Value getValue(const int idx) const {
|
||||
return data[idx].val;
|
||||
}
|
||||
|
||||
const KeyVal& getKeyValue(const int idx) const {
|
||||
return data[idx];
|
||||
}
|
||||
|
||||
const KeyVal& operator [] (const int idx) const {
|
||||
return data[idx];
|
||||
}
|
||||
|
||||
const KeyVal& front() const {return data.front();}
|
||||
const KeyVal& back() const {return data.back();}
|
||||
|
||||
/** get the range (min/max) for the key-data (x-axes) */
|
||||
Range getKeyRange() const {
|
||||
Range range(+INFINITY,-INFINITY);
|
||||
for (const KeyVal& kv : data) {range.adjust(kv.key);}
|
||||
return range;
|
||||
}
|
||||
|
||||
/** get the range (min/max) for the value-data (y-axes) */
|
||||
Range getValueRange() const {
|
||||
Range range(+INFINITY,-INFINITY);
|
||||
for (const KeyVal& kv : data) {range.adjust(kv.val);}
|
||||
return range;
|
||||
}
|
||||
|
||||
/** get the number of entries */
|
||||
size_t size() const {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // PLOT_DATA_H
|
||||
215
ui/debug/plot/Plot.h
Normal file
215
ui/debug/plot/Plot.h
Normal file
@@ -0,0 +1,215 @@
|
||||
#ifndef PLOT_H
|
||||
#define PLOT_H
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "Axes.h"
|
||||
#include "Data.h"
|
||||
|
||||
#include <Indoor/geo/Point2.h>
|
||||
|
||||
/** describes a plot-setup */
|
||||
struct PlotParameters {
|
||||
|
||||
AxesX xAxes;
|
||||
AxesY yAxes;
|
||||
|
||||
int w;
|
||||
int h;
|
||||
|
||||
/** helper method */
|
||||
Point2 getPoint(const typename Data::KeyVal& kv) const {
|
||||
const float x1 = xAxes.convert(kv.key);
|
||||
const float y1 = yAxes.convert(kv.val);
|
||||
return Point2(x1, y1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/** interface for all plots */
|
||||
class Plot {
|
||||
|
||||
protected:
|
||||
|
||||
Data data;
|
||||
|
||||
QColor bg = QColor(255,255,255,128);
|
||||
|
||||
public:
|
||||
|
||||
virtual ~Plot() {;}
|
||||
|
||||
void render(QPainter& p, const PlotParameters& params) {
|
||||
// maybe do something here?
|
||||
renderSub(p, params);
|
||||
}
|
||||
|
||||
Data& getData() {return data;}
|
||||
|
||||
Range getKeyRange() const {return data.getKeyRange();}
|
||||
|
||||
Range getValueRange() const {return data.getValueRange();}
|
||||
|
||||
protected:
|
||||
|
||||
/** subclasses must render themselves here */
|
||||
virtual void renderSub(QPainter& p, const PlotParameters& params) = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** combine several plots (several lines, points, ...) together into one plot */
|
||||
class PlotContainer {
|
||||
|
||||
private:
|
||||
|
||||
PlotParameters params;
|
||||
|
||||
std::vector<Plot*> plots;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
PlotContainer() {
|
||||
;
|
||||
}
|
||||
|
||||
AxesX& getAxesX() {return params.xAxes;}
|
||||
AxesY& getAxesY() {return params.yAxes;}
|
||||
|
||||
Range overwriteRangeKey = Range(0,0);
|
||||
Range overwriteRangeVal = Range(0,0);
|
||||
|
||||
void setValRange(const Range& range) {overwriteRangeVal = range;}
|
||||
void setKeyRange(const Range& range) {overwriteRangeKey = range;}
|
||||
|
||||
|
||||
void resize(const int w, const int h) {
|
||||
params.w = w;
|
||||
params.h = h;
|
||||
}
|
||||
|
||||
void addPlot(Plot* plt) {
|
||||
plots.push_back(plt);
|
||||
}
|
||||
|
||||
void render(QPainter& p) {
|
||||
|
||||
setupAxes();
|
||||
|
||||
QColor bg(255,255,255,192);
|
||||
p.fillRect(0,0,params.w,params.h,bg);
|
||||
p.setPen(Qt::black);
|
||||
p.drawRect(0,0,params.w-1,params.h-1);
|
||||
|
||||
for (Plot* plt : plots) {
|
||||
plt->render(p, params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setupAxes() {
|
||||
|
||||
params.xAxes.setPixels(params.w);
|
||||
params.yAxes.setPixels(params.h);
|
||||
|
||||
Range keyRange(+INFINITY,-INFINITY);
|
||||
Range valRange(+INFINITY,-INFINITY);
|
||||
|
||||
if (overwriteRangeKey.isValid()) {keyRange.adjust(overwriteRangeKey);}
|
||||
if (overwriteRangeVal.isValid()) {valRange.adjust(overwriteRangeVal);}
|
||||
|
||||
/** calculate min/max for both x and y axis */
|
||||
for (Plot* plt : plots) {
|
||||
if (!overwriteRangeKey.isValid()) {keyRange.adjust(plt->getKeyRange());}
|
||||
if (!overwriteRangeVal.isValid()) {valRange.adjust(plt->getValueRange());}
|
||||
}
|
||||
|
||||
valRange.scale(1.1);
|
||||
|
||||
params.xAxes.setRange(keyRange);
|
||||
params.yAxes.setRange(valRange);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class LinePlot : public Plot {
|
||||
|
||||
private:
|
||||
|
||||
QColor lineColor = QColor(0,0,255);
|
||||
|
||||
public:
|
||||
|
||||
void setColor(const QColor c) {
|
||||
this->lineColor = c;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void renderSub(QPainter& p , const PlotParameters& params) override {
|
||||
|
||||
p.setPen(lineColor);
|
||||
|
||||
for (int i = 0; i < (int) data.size()-1; ++i) {
|
||||
|
||||
const typename Data::KeyVal kv1 = data[i+0];
|
||||
const typename Data::KeyVal kv2 = data[i+1];
|
||||
|
||||
const Point2 p1 = params.getPoint(kv1);
|
||||
const Point2 p2 = params.getPoint(kv2);
|
||||
|
||||
p.drawLine(p1.x, p1.y, p2.x, p2.y);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
class PointPlot : public Plot {
|
||||
|
||||
private:
|
||||
|
||||
QColor pointColor = QColor(0,0,255);
|
||||
float pointSize = 4;
|
||||
|
||||
public:
|
||||
|
||||
void setColor(const QColor c) {
|
||||
this->pointColor = c;
|
||||
}
|
||||
|
||||
void setPointSize(const float size) {
|
||||
this->pointSize = size;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void renderSub(QPainter& p , const PlotParameters& params) override {
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(pointColor);
|
||||
|
||||
for (int i = 0; i < (int) data.size(); ++i) {
|
||||
|
||||
const typename Data::KeyVal kv1 = data[i+0];
|
||||
|
||||
const Point2 p1 = params.getPoint(kv1);
|
||||
|
||||
p.drawEllipse(p1.x, p1.y, pointSize, pointSize);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // PLOT_H
|
||||
37
ui/debug/plot/PlottWidget.cpp
Normal file
37
ui/debug/plot/PlottWidget.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "PlottWidget.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
|
||||
PlotWidget::PlotWidget(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
setMinimumSize(100, 100);
|
||||
|
||||
// LinePlot* lp = new LinePlot();
|
||||
// pc.addPlot(lp);
|
||||
|
||||
// lp->getData().add(1, 1);
|
||||
// lp->getData().add(2, 2);
|
||||
// lp->getData().add(3, 3);
|
||||
// lp->getData().add(4, 1);
|
||||
// lp->getData().add(5, 2);
|
||||
// lp->getData().add(6, 3);
|
||||
// lp->getData().add(7, 1);
|
||||
// lp->getData().add(8, 2);
|
||||
// lp->getData().add(9, 3);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void PlotWidget::resizeEvent(QResizeEvent* evt) {
|
||||
(void) evt;
|
||||
pc.resize(width(), height());
|
||||
}
|
||||
|
||||
void PlotWidget::paintEvent(QPaintEvent* evt) {
|
||||
(void) evt;
|
||||
QPainter p(this);
|
||||
pc.render(p);
|
||||
p.end();
|
||||
}
|
||||
30
ui/debug/plot/PlottWidget.h
Normal file
30
ui/debug/plot/PlottWidget.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef PLOTTI_H
|
||||
#define PLOTTI_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "Plot.h"
|
||||
|
||||
/** widget to render one plot */
|
||||
class PlotWidget : public QWidget {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
explicit PlotWidget(QWidget *parent = 0);
|
||||
|
||||
protected:
|
||||
|
||||
PlotContainer pc;
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
void paintEvent(QPaintEvent*);
|
||||
void resizeEvent(QResizeEvent*);
|
||||
|
||||
};
|
||||
|
||||
#endif // PLOTTI_H
|
||||
48
ui/debug/plot/Range.h
Normal file
48
ui/debug/plot/Range.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef PLOT_RANGE_H
|
||||
#define PLOT_RANGE_H
|
||||
|
||||
struct Range {
|
||||
|
||||
float min;
|
||||
float max;
|
||||
|
||||
Range() : min(0), max(0) {;}
|
||||
|
||||
Range(const float min, const float max) : min(min), max(max) {
|
||||
;
|
||||
}
|
||||
|
||||
float getSize() const {
|
||||
return max-min;
|
||||
}
|
||||
|
||||
float getCenter() const {
|
||||
return (max+min)/2;
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return getSize() > 0;
|
||||
}
|
||||
|
||||
/** resize the region. 1.0 = keep-as-is */
|
||||
void scale(const float val) {
|
||||
const float center = getCenter();
|
||||
const float size = getSize();
|
||||
min = center - size / 2 * val;
|
||||
max = center + size / 2 * val;
|
||||
}
|
||||
|
||||
/** adjust (grow) the range */
|
||||
void adjust(const float val) {
|
||||
if (val < min) {min = val;}
|
||||
if (val > max) {max = val;}
|
||||
}
|
||||
|
||||
void adjust(const Range& o) {
|
||||
if (o.min < min) {min = o.min;}
|
||||
if (o.max > max) {max = o.max;}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // PLOT_RANGE_H
|
||||
73
ui/dialog/LoadSetupDialog.cpp
Normal file
73
ui/dialog/LoadSetupDialog.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "LoadSetupDialog.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QListView>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QStringListModel>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <Indoor/Assertions.h>
|
||||
|
||||
#include "../Config.h"
|
||||
|
||||
LoadSetupDialog::LoadSetupDialog() {
|
||||
|
||||
// the folder all map-setups reside within
|
||||
const std::string base = Config::getMapDir();
|
||||
QDir mapFolder(QString(base.c_str()));
|
||||
|
||||
// sanity check. folder must exist
|
||||
Assert::isTrue(mapFolder.exists(), "folder not found: " + base);
|
||||
|
||||
// get all subfolders (each subfolder descibres one setup), skip the first two folders: "." and ".."
|
||||
QStringList subfolders = mapFolder.entryList(QDir::Dirs);
|
||||
subfolders.removeFirst();
|
||||
subfolders.removeFirst();
|
||||
// for (int i = 2; i < subfolders.size(); ++i) {
|
||||
// const QString subfolder = subfolders[i];
|
||||
// std::cout << subfolder.toStdString() << std::endl;
|
||||
// }
|
||||
|
||||
int w = 350;
|
||||
int h = 350;
|
||||
|
||||
const QFont font("Arial", 20);
|
||||
|
||||
QListView* lst = new QListView(this);
|
||||
lst->setGeometry(5,5,w-5-5,h-5-5);
|
||||
lst->setFont(font);
|
||||
|
||||
QStringListModel* mdl = new QStringListModel(subfolders);
|
||||
lst->setModel(mdl);
|
||||
|
||||
// list item selected
|
||||
connect(lst, &QListView::clicked, [this, base, subfolders] (const QModelIndex& idx) {
|
||||
const int i = idx.row();
|
||||
selDir = base + subfolders[i].toStdString();
|
||||
this->close();
|
||||
});
|
||||
|
||||
// QPushButton* btnOK = new QPushButton(this);
|
||||
// btnOK->setText("OK");
|
||||
// btnOK->setGeometry(5,h-32-5,w-5-5,32);
|
||||
|
||||
// // OK button clicked
|
||||
// btnOK->connect(btnOK, &QPushButton::clicked, [&] () {
|
||||
// this->close();
|
||||
// });
|
||||
|
||||
this->resize(w,h);
|
||||
|
||||
}
|
||||
|
||||
QDir LoadSetupDialog::pickSetupFolder() {
|
||||
|
||||
|
||||
LoadSetupDialog dlg;
|
||||
dlg.exec();
|
||||
return QDir(QString(dlg.selDir.c_str()));
|
||||
|
||||
}
|
||||
25
ui/dialog/LoadSetupDialog.h
Normal file
25
ui/dialog/LoadSetupDialog.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef LOADSETUPDIALOG_H
|
||||
#define LOADSETUPDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDir>
|
||||
|
||||
class LoadSetupDialog : public QDialog {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
/** hidden ctor */
|
||||
explicit LoadSetupDialog();
|
||||
|
||||
std::string selDir = "";
|
||||
|
||||
public:
|
||||
|
||||
/** show a dialog to open a data-folder */
|
||||
static QDir pickSetupFolder();
|
||||
|
||||
};
|
||||
|
||||
#endif // LOADSETUPDIALOG_H
|
||||
222
ui/map/FloorRenderer.h
Normal file
222
ui/map/FloorRenderer.h
Normal file
@@ -0,0 +1,222 @@
|
||||
#ifndef FLOORRENDERER_H
|
||||
#define FLOORRENDERER_H
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "GL.h"
|
||||
|
||||
|
||||
class FloorRenderer : protected QOpenGLFunctions {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::Floor* floor;
|
||||
|
||||
QOpenGLBuffer arrayBuf;
|
||||
QOpenGLBuffer indexBuf;
|
||||
QOpenGLTexture* texture = nullptr;
|
||||
|
||||
std::vector<VertNormTex> vertices;
|
||||
std::vector<GLushort> indices;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
FloorRenderer(Floorplan::Floor* floor) : floor(floor), arrayBuf(QOpenGLBuffer::VertexBuffer), indexBuf(QOpenGLBuffer::IndexBuffer) {
|
||||
;
|
||||
}
|
||||
|
||||
/** dctor */
|
||||
~FloorRenderer() {
|
||||
arrayBuf.destroy();
|
||||
indexBuf.destroy();
|
||||
delete texture;
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void render(QOpenGLShaderProgram *program) {
|
||||
|
||||
// Tell OpenGL which VBOs to use
|
||||
arrayBuf.bind();
|
||||
indexBuf.bind();
|
||||
texture->bind();
|
||||
|
||||
// vertices
|
||||
int vertLoc = program->attributeLocation("a_position");
|
||||
program->enableAttributeArray(vertLoc);
|
||||
program->setAttributeBuffer(vertLoc, GL_FLOAT, vertices[0].getVertOffset(), 3, sizeof(vertices[0]));
|
||||
|
||||
// Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
|
||||
int normLoc = program->attributeLocation("a_normal");
|
||||
program->enableAttributeArray(normLoc);
|
||||
program->setAttributeBuffer(normLoc, GL_FLOAT, vertices[0].getNormOffset(), 3, sizeof(vertices[0]));
|
||||
|
||||
int texcoordLocation = program->attributeLocation("a_texcoord");
|
||||
program->enableAttributeArray(texcoordLocation);
|
||||
program->setAttributeBuffer(texcoordLocation, GL_FLOAT, vertices[0].getTexOffset(), 2, sizeof(vertices[0]));
|
||||
|
||||
// texture
|
||||
program->setUniformValue("texture", 0);
|
||||
|
||||
// Draw cube geometry using indices from VBO 1
|
||||
glDrawElements(GL_QUADS, indices.size(), GL_UNSIGNED_SHORT, 0);
|
||||
|
||||
}
|
||||
|
||||
void initGL() {
|
||||
initializeOpenGLFunctions();
|
||||
build();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
|
||||
QImage img(":/res/gl/tex/wall1.jpg");
|
||||
texture = new QOpenGLTexture(img);
|
||||
texture->setMinificationFilter(QOpenGLTexture::Nearest);
|
||||
texture->setMagnificationFilter(QOpenGLTexture::Linear);
|
||||
texture->setWrapMode(QOpenGLTexture::Repeat);
|
||||
|
||||
// build array of vertices and indices
|
||||
for (Floorplan::FloorObstacle* obstacle : floor->obstacles) {
|
||||
add(floor, obstacle, vertices, indices);
|
||||
}
|
||||
|
||||
// Transfer vertex data to VBO 0
|
||||
arrayBuf.create();
|
||||
arrayBuf.bind();
|
||||
arrayBuf.allocate(vertices.data(), vertices.size() * sizeof(vertices[0]));
|
||||
|
||||
// Transfer index data to VBO 1
|
||||
indexBuf.create();
|
||||
indexBuf.bind();
|
||||
indexBuf.allocate(indices.data(), indices.size() * sizeof(indices[0]));
|
||||
|
||||
}
|
||||
|
||||
void add(Floorplan::Floor* floor, Floorplan::FloorObstacle* obstacle, std::vector<VertNormTex>& vertices, std::vector<GLushort>& indices) {
|
||||
|
||||
if (dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle)) {
|
||||
|
||||
Floorplan::FloorObstacleLine* line = dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle);
|
||||
if (line->type != Floorplan::ObstacleType::WALL) {return;}
|
||||
|
||||
// QVector3D p1(line->from.x, floor->getStartingZ(), line->from.y);
|
||||
// QVector3D p2(line->to.x, floor->getStartingZ(), line->to.y);
|
||||
// QVector3D p3(line->to.x, floor->getEndingZ(), line->to.y);
|
||||
// QVector3D p4(line->from.x, floor->getEndingZ(), line->from.y);
|
||||
|
||||
// addFace(p1,p2,p3,p4, vertices, indices);
|
||||
|
||||
|
||||
addFace(line->from, line->to, floor->getStartingZ(), floor->getEndingZ(), vertices, indices);
|
||||
addFace(line->to, line->from, floor->getStartingZ(), floor->getEndingZ(), vertices, indices);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void addFace(const Point2 from, const Point2 to, const float h1, const float h2, std::vector<VertNormTex>& vertices, std::vector<GLushort>& indices) {
|
||||
|
||||
// unit-face with unit normal (facing the camera)
|
||||
QVector3D p1(-0.5, -0.0, 0);
|
||||
QVector3D p2(+0.5, -0.0, 0);
|
||||
QVector3D p3(+0.5, +1.0, 0);
|
||||
QVector3D p4(-0.5, +1.0, 0);
|
||||
QVector3D norm(0, 0, 1);
|
||||
|
||||
// how to scale the unit-face to match the wall
|
||||
const float h = h2-h1;
|
||||
const float s = from.getDistance(to);
|
||||
QMatrix4x4 scale; scale.scale(s, h, 1);
|
||||
|
||||
// how to rotate the unit-face to match the wall
|
||||
const float angle = std::atan2(to.y - from.y, to.x - from.x);
|
||||
const float deg = angle * 180 / M_PI;
|
||||
QMatrix4x4 rot; rot.rotate(deg, QVector3D(0,1,0));
|
||||
|
||||
// how to translate the unit-face to match the wall
|
||||
const Point2 center = (from + to) / 2;
|
||||
QMatrix4x4 move; move.translate(center.x, h1, center.y);
|
||||
|
||||
// final matrix
|
||||
QMatrix4x4 mat = move * rot * scale;
|
||||
|
||||
// texture coordinates (scale only)
|
||||
const QVector2D tex1 = (scale * p1).toVector2D() / 5;
|
||||
const QVector2D tex2 = (scale * p2).toVector2D() / 5;
|
||||
const QVector2D tex3 = (scale * p3).toVector2D() / 5;
|
||||
const QVector2D tex4 = (scale * p4).toVector2D() / 5;
|
||||
|
||||
// modify vertices
|
||||
p1 = mat * p1;
|
||||
p2 = mat * p2;
|
||||
p3 = mat * p3;
|
||||
p4 = mat * p4;
|
||||
norm = (rot * norm).normalized();
|
||||
|
||||
const int start = vertices.size();
|
||||
vertices.push_back(VertNormTex(p1, norm, tex1));
|
||||
vertices.push_back(VertNormTex(p2, norm, tex2));
|
||||
vertices.push_back(VertNormTex(p3, norm, tex3));
|
||||
vertices.push_back(VertNormTex(p4, norm, tex4));
|
||||
|
||||
indices.push_back(start+0);
|
||||
indices.push_back(start+1);
|
||||
indices.push_back(start+2);
|
||||
indices.push_back(start+3);
|
||||
|
||||
}
|
||||
|
||||
// void addFace(QVector3D p1, QVector3D p2, QVector3D p3, QVector3D p4, std::vector<VertNorm>& vertices, std::vector<GLushort>& indices) {
|
||||
|
||||
// const float s = 50;
|
||||
|
||||
// // ensure camera facing (for culling)
|
||||
// if (p1.x() != p2.x()) {
|
||||
// if (p1.x() > p2.x()) {std::swap(p1, p2), std::swap(p3, p4);}
|
||||
// } else {
|
||||
// if (p1.z() > p2.z()) {std::swap(p1, p2), std::swap(p3, p4);}
|
||||
// }
|
||||
|
||||
// // corresponding normal vector
|
||||
// QVector3D norm = QVector3D::crossProduct((p2-p1), (p3-p1)).normalized();
|
||||
|
||||
// // orient towards the viewport
|
||||
// const QVector3D view(-99,-99,-99);
|
||||
// if ((view-norm).length() > (view+norm).length()) {norm = -norm;}
|
||||
|
||||
|
||||
// const int start = vertices.size();
|
||||
|
||||
// indices.push_back(start+0);
|
||||
// indices.push_back(start+1);
|
||||
// indices.push_back(start+2);
|
||||
// indices.push_back(start+3);
|
||||
|
||||
// vertices.push_back(VertNorm(p1/s, norm));
|
||||
// vertices.push_back(VertNorm(p2/s, norm));
|
||||
// vertices.push_back(VertNorm(p3/s, norm));
|
||||
// vertices.push_back(VertNorm(p4/s, norm));
|
||||
|
||||
// // and the other side (clockwise, negated normal)
|
||||
|
||||
// indices.push_back(start+0);
|
||||
// indices.push_back(start+3);
|
||||
// indices.push_back(start+2);
|
||||
// indices.push_back(start+1);
|
||||
|
||||
// vertices.push_back(VertNorm(p1/s, -norm));
|
||||
// vertices.push_back(VertNorm(p2/s, -norm));
|
||||
// vertices.push_back(VertNorm(p3/s, -norm));
|
||||
// vertices.push_back(VertNorm(p4/s, -norm));
|
||||
|
||||
// }
|
||||
|
||||
};
|
||||
|
||||
#endif // FLOORRENDERER_H
|
||||
266
ui/map/MapView.cpp
Normal file
266
ui/map/MapView.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#include "MapView.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QGLShaderProgram>
|
||||
|
||||
#include "elements/Walls.h"
|
||||
#include "elements/Ground.h"
|
||||
#include "elements/Handrails.h"
|
||||
#include "elements/Stairs.h"
|
||||
#include "elements/Doors.h"
|
||||
#include "elements/Path.h"
|
||||
#include "elements/ColorPoints.h"
|
||||
#include "elements/Object.h"
|
||||
|
||||
#include <Indoor/data/Timestamp.h>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* before adding elements to the MapView via setMap(),
|
||||
* the MapViews openGL context must be initialized
|
||||
* that means: the MapView must have been added to a window,
|
||||
* which is already visible!
|
||||
*/
|
||||
|
||||
MapView::MapView(QWidget* parent) : QOpenGLWidget(parent) {
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
void MapView::clear() {
|
||||
|
||||
for (Renderable* r : elements) {delete r;}
|
||||
elements.clear();
|
||||
|
||||
}
|
||||
|
||||
void MapView::setMap(Floorplan::IndoorMap* map) {
|
||||
|
||||
clear();
|
||||
|
||||
if (!isGLInitialized) {throw Exception("openGL is not yet initialized. add mapView to a visible window!");}
|
||||
|
||||
// first to be rendered
|
||||
this->colorPoints = new ColorPoints();
|
||||
elements.push_back(this->colorPoints);
|
||||
|
||||
//leDude = new Object("/mnt/firma/tmp/3D/minion/minion.obj", "/mnt/firma/tmp/3D/minion/minion.png", "", 0.35);
|
||||
leDude = new Object("/mnt/firma/tmp/3D/gnome/gnome.obj", "/mnt/firma/tmp/3D/gnome/gnome_diffuse.jpg", "/mnt/firma/tmp/3D/gnome/gnome_normal.jpg", 0.033);
|
||||
//leDude = new Object("/mnt/firma/tmp/3D/squirrel/squirrel.obj", "/mnt/firma/tmp/3D/squirrel/squirrel.jpg", "/mnt/firma/tmp/3D/squirrel/squirrel_normal.jpg", 0.033);
|
||||
elements.push_back(leDude);
|
||||
|
||||
for (Floorplan::Floor* floor : map->floors) {
|
||||
elements.push_back(new Ground(floor));
|
||||
elements.push_back(new Walls(floor));
|
||||
elements.push_back(new Handrails(floor));
|
||||
elements.push_back(new Stairs(floor));
|
||||
elements.push_back(new Doors(floor));
|
||||
}
|
||||
|
||||
this->path = new Path();
|
||||
elements.push_back(this->path);
|
||||
|
||||
|
||||
|
||||
// initialize the OpenGL context of all contained elements
|
||||
for (Renderable* r : elements) {
|
||||
r->initGL();
|
||||
}
|
||||
|
||||
// i want the focus! needed for key-events
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
}
|
||||
|
||||
void MapView::setPath(const std::vector<Point3>& path) {
|
||||
this->path->set(path);
|
||||
}
|
||||
|
||||
|
||||
void MapView::timerEvent(QTimerEvent *) {
|
||||
update();
|
||||
}
|
||||
|
||||
void MapView::initializeGL() {
|
||||
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
// basic config
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
// start background update timer
|
||||
const int fps = 25;
|
||||
const int interval = 1000 / fps;
|
||||
timer.start(interval, this);
|
||||
|
||||
// OpenGL is now initialized
|
||||
isGLInitialized = true;
|
||||
|
||||
}
|
||||
|
||||
void MapView::paintGL() {
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
draw();
|
||||
}
|
||||
|
||||
void MapView::resizeGL(int w, int h) {
|
||||
|
||||
// Calculate aspect ratio
|
||||
qreal aspect = qreal(w) / qreal(h ? h : 1);
|
||||
|
||||
// viewing frustrum [0:50] meter
|
||||
const qreal zNear = 0.02, zFar = 50, fov = 50.0;
|
||||
|
||||
// Reset projection
|
||||
matProject.setToIdentity();
|
||||
matProject.scale(-1, 1, 1);
|
||||
glCullFace(GL_FRONT);
|
||||
//matProject.scale(0.05, 0.05, 0.05);
|
||||
matProject.perspective(fov, aspect, zNear, zFar);
|
||||
//matProject.scale(-0.01, 0.01, 0.01);
|
||||
|
||||
}
|
||||
|
||||
void MapView::rebuildLookat() {
|
||||
// QVector3D qDir(lookAt.dir.x, lookAt.dir.z, lookAt.dir.y);
|
||||
// QVector3D at = QVector3D(lookAt.pos.x, lookAt.pos.z, lookAt.pos.y);
|
||||
// QVector3D eye = at + qDir * 0.1;
|
||||
// QVector3D up = QVector3D(0,1,0);
|
||||
// matView.setToIdentity();
|
||||
// //matView.scale(0.01, 0.01, 0.01);
|
||||
// matView.lookAt(eye, at, up);
|
||||
// //matView.scale(0.99, 1, 1);
|
||||
// //matView.translate(0.7, 0, 0);
|
||||
// lightPos = eye + QVector3D(0.0, 4.0, 0.0);
|
||||
// eyePos = eye;
|
||||
|
||||
const Point3 dir = lookAt.getDir();
|
||||
|
||||
QVector3D qDir(dir.x, dir.z, dir.y);
|
||||
QVector3D eye(lookAt.eye_m.x, lookAt.eye_m.z, lookAt.eye_m.y);
|
||||
QVector3D at = eye + qDir * 0.5;
|
||||
QVector3D up = QVector3D(0,1,0);
|
||||
matView.setToIdentity();
|
||||
matView.lookAt(eye, at, up);
|
||||
lightPos = eye + QVector3D(0.0, 0.5, 0.0) + qDir * 1.2;
|
||||
eyePos = eye;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MapView::setCurrentEstimation(const Point3 pos, const Point3 dir) {
|
||||
const float angle = std::atan2(dir.y, dir.x) * 180 / M_PI;
|
||||
if (leDude) {
|
||||
leDude->setPosition(pos.x, pos.y, pos.z);
|
||||
leDude->setRotation(0, 0, -angle + 90);
|
||||
}
|
||||
}
|
||||
|
||||
void MapView::setLookAt(const Point3 pos_m, const Point3 dir) {
|
||||
lookAt.eye_m = pos_m + dir * 0.1;
|
||||
lookAt.dir = dir;
|
||||
rebuildLookat();
|
||||
}
|
||||
|
||||
void MapView::setLookDir(const Point3 dir) {
|
||||
lookAt.dir = dir;
|
||||
rebuildLookat();
|
||||
}
|
||||
|
||||
void MapView::setLookEye(const Point3 eye_m) {
|
||||
lookAt.eye_m = eye_m;
|
||||
rebuildLookat();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MapView::mousePressEvent(QMouseEvent* evt) {
|
||||
mouseState.down = true;
|
||||
mouseState.x = evt->x();
|
||||
mouseState.y = evt->y();
|
||||
}
|
||||
|
||||
void MapView::mouseMoveEvent(QMouseEvent* evt) {
|
||||
|
||||
const float dx = evt->x() - mouseState.x;
|
||||
const float dy = evt->y() - mouseState.y;
|
||||
|
||||
// PI*0.3 head movement left/right and up/down
|
||||
const float yFac = (this->height() / 2) / (M_PI * 0.3);
|
||||
const float xFac = (this->width() / 2) / (M_PI * 0.3);
|
||||
|
||||
lookAt.dirOffset = Point3(0, std::sin(dx/xFac), std::sin(-dy/yFac));
|
||||
rebuildLookat();
|
||||
|
||||
}
|
||||
|
||||
void MapView::mouseReleaseEvent(QMouseEvent* evt) {
|
||||
mouseState.down = false;
|
||||
}
|
||||
|
||||
|
||||
void MapView::keyPressEvent(QKeyEvent* evt) {
|
||||
|
||||
if (evt->key() == Qt::Key_W) {lookAt.eye_m += lookAt.getDir(); rebuildLookat();}
|
||||
if (evt->key() == Qt::Key_S) {lookAt.eye_m -= lookAt.getDir(); rebuildLookat();}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MapView::toggleRenderMode() {
|
||||
|
||||
renderMode = (RenderMode) (((int)renderMode + 1) % 3);
|
||||
|
||||
for (Renderable* r : elements) {
|
||||
if (renderMode == RenderMode::OUTLINE) {
|
||||
r->setOutlineOnly(true);
|
||||
} else {
|
||||
r->setOutlineOnly(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MapView::draw() {
|
||||
|
||||
//const Timestamp ts1 = Timestamp::fromUnixTime();
|
||||
|
||||
// clear everything
|
||||
glClearColor(0,0,0,1);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
if (renderMode == RenderMode::TRANSPARENT) {
|
||||
glEnable(GL_BLEND);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
||||
|
||||
|
||||
|
||||
for (Renderable* r : elements) {
|
||||
|
||||
QOpenGLShaderProgram& program = r->getProgram();
|
||||
program.bind();
|
||||
|
||||
// set the matrices
|
||||
program.setUniformValue("m_matrix", r->modelMatrix.mat);
|
||||
program.setUniformValue("mv_matrix", matView * r->modelMatrix.mat);
|
||||
program.setUniformValue("mvp_matrix", matProject * matView * r->modelMatrix.mat);
|
||||
program.setUniformValue("lightWorldPos", lightPos);
|
||||
program.setUniformValue("eyeWorldPos", eyePos);
|
||||
|
||||
r->render();
|
||||
|
||||
}
|
||||
|
||||
//const Timestamp ts2 = Timestamp::fromUnixTime();
|
||||
//qDebug("%d ms", (ts2-ts1).ms());
|
||||
|
||||
|
||||
}
|
||||
152
ui/map/MapView.h
Normal file
152
ui/map/MapView.h
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifndef MAPVIEW_H
|
||||
#define MAPVIEW_H
|
||||
|
||||
#include <../misc/fixc11.h>
|
||||
|
||||
|
||||
#include <QOpenGLWidget>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QBasicTimer>
|
||||
|
||||
#include <Indoor/geo/Point3.h>
|
||||
#include <Indoor/nav/dijkstra/DijkstraPath.h>
|
||||
|
||||
#include "elements/Path.h"
|
||||
#include "elements/ColorPoints.h"
|
||||
#include "elements/Object.h"
|
||||
|
||||
#include "../nav/State.h"
|
||||
|
||||
namespace Floorplan {
|
||||
class IndoorMap;
|
||||
}
|
||||
|
||||
class Renderable;
|
||||
class Path;
|
||||
|
||||
|
||||
class MapView : public QOpenGLWidget, protected QOpenGLFunctions {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
QMatrix4x4 matProject;
|
||||
QMatrix4x4 matView;
|
||||
|
||||
QVector3D lightPos;
|
||||
QVector3D eyePos;
|
||||
|
||||
|
||||
QBasicTimer timer;
|
||||
|
||||
std::vector<Renderable*> elements;
|
||||
Path* path = nullptr;
|
||||
ColorPoints* colorPoints = nullptr;
|
||||
Object* leDude = nullptr;
|
||||
|
||||
struct LookAt {
|
||||
Point3 eye_m = Point3(0,0,1);
|
||||
Point3 dir = Point3(1,0,-0.1);
|
||||
Point3 dirOffset = Point3(0,0,0);
|
||||
Point3 getDir() const {return dir + dirOffset;}
|
||||
} lookAt;
|
||||
|
||||
struct MouseState {
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
bool down = false;
|
||||
} mouseState;
|
||||
|
||||
void rebuildLookat();
|
||||
|
||||
void clear();
|
||||
|
||||
public:
|
||||
|
||||
MapView(QWidget* parent = 0);
|
||||
|
||||
/** set the map to display */
|
||||
void setMap(Floorplan::IndoorMap* map);
|
||||
|
||||
/** the position to look at + looking direction */
|
||||
void setLookAt(const Point3 pos, const Point3 dir = Point3(1, 0, -0.1));
|
||||
|
||||
/** set the eye's looking direction (looking from eye into this direction) */
|
||||
void setLookDir(const Point3 dir);
|
||||
|
||||
/** set the eye's position (looking from here) */
|
||||
void setLookEye(const Point3 eye_m);
|
||||
|
||||
|
||||
/** set the currently estimated position */
|
||||
void setCurrentEstimation(const Point3 pos, const Point3 dir);
|
||||
|
||||
/** set the path to disply */
|
||||
void setPath(const std::vector<Point3>& path);
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
/** set the path to disply */
|
||||
Q_INVOKABLE void setPath(const void* path) {
|
||||
setPath( (const DijkstraPath<MyGridNode>*) path);
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
/** set the path to disply */
|
||||
template <typename Node> void setPath(const DijkstraPath<Node>* path) {
|
||||
this->path->set(*path);
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
void showGridImportance(Grid<MyGridNode>* grid) {
|
||||
this->colorPoints->setFromGridImportance(grid);
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
Q_INVOKABLE void showParticles(const void* particles) {
|
||||
showParticles((const std::vector<K::Particle<MyState>>*) particles);
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
void showParticles(const std::vector<K::Particle<MyState>>* particles) {
|
||||
this->colorPoints->setFromParticles(*particles);
|
||||
}
|
||||
|
||||
enum RenderMode {
|
||||
NORMAL,
|
||||
TRANSPARENT,
|
||||
OUTLINE,
|
||||
};
|
||||
|
||||
RenderMode renderMode = RenderMode::NORMAL;
|
||||
|
||||
void toggleRenderMode();
|
||||
|
||||
public slots:
|
||||
|
||||
void mousePressEvent(QMouseEvent*);
|
||||
void mouseMoveEvent(QMouseEvent*);
|
||||
void mouseReleaseEvent(QMouseEvent*);
|
||||
|
||||
void keyPressEvent(QKeyEvent*);
|
||||
|
||||
protected:
|
||||
|
||||
void timerEvent(QTimerEvent *e) Q_DECL_OVERRIDE;
|
||||
|
||||
void initializeGL();
|
||||
|
||||
void paintGL();
|
||||
|
||||
void resizeGL(int width, int height);
|
||||
|
||||
private:
|
||||
|
||||
bool isGLInitialized = false;
|
||||
|
||||
void draw();
|
||||
|
||||
};
|
||||
|
||||
#endif // MAPVIEW_H
|
||||
76
ui/map/Renderable.h
Normal file
76
ui/map/Renderable.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef RENDERABLE_H
|
||||
#define RENDERABLE_H
|
||||
|
||||
#include <QOpenGLShaderProgram>
|
||||
|
||||
class Renderable {
|
||||
|
||||
protected:
|
||||
|
||||
QOpenGLShaderProgram program;
|
||||
|
||||
public:
|
||||
|
||||
/** dtor */
|
||||
virtual ~Renderable() {;}
|
||||
|
||||
/** get the renderable's shader */
|
||||
QOpenGLShaderProgram& getProgram() {return program;}
|
||||
|
||||
/** render the renderable */
|
||||
void render() {
|
||||
program.bind();
|
||||
_render();
|
||||
}
|
||||
|
||||
struct ModelMatrix {
|
||||
QVector3D pos = QVector3D(0,0,0);
|
||||
QVector3D rot = QVector3D(0,0,0);
|
||||
QVector3D scale = QVector3D(1,1,1);
|
||||
QMatrix4x4 mat;
|
||||
ModelMatrix() {mat.setToIdentity();}
|
||||
void update() {
|
||||
const QVector3D _rot = rot.normalized();
|
||||
const float rotDeg = rot.length();
|
||||
mat.setToIdentity();
|
||||
mat.scale(scale.x(), scale.y(), scale.z());
|
||||
mat.translate(pos.x(), pos.y(), pos.z());
|
||||
mat.rotate(rotDeg, _rot.x(), _rot.y(), _rot.z());
|
||||
}
|
||||
} modelMatrix;
|
||||
|
||||
void setPosition(QVector3D vec) {
|
||||
modelMatrix.pos = vec * 0.99;
|
||||
modelMatrix.update();
|
||||
}
|
||||
|
||||
void setPosition(const float x, const float y, const float z) {
|
||||
setPosition(QVector3D(x,z,y));
|
||||
}
|
||||
|
||||
/** in degrees! */
|
||||
void setRotation(const float x, const float y, const float z) {
|
||||
modelMatrix.rot = QVector3D(x,z,y);
|
||||
modelMatrix.update();
|
||||
}
|
||||
|
||||
virtual void setOutlineOnly(const bool outline) {;}
|
||||
|
||||
virtual void initGL() = 0;
|
||||
|
||||
virtual void _render() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
/** helper method to build the shader */
|
||||
void loadShader(const QString& vertex, const QString& fragment) {
|
||||
program.removeAllShaders();
|
||||
if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, vertex)) {throw "1";}
|
||||
if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, fragment)) {throw "2";}
|
||||
if (!program.link()) {throw "3";}
|
||||
if (!program.bind()) {throw "4";}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // RENDERABLE_H
|
||||
114
ui/map/elements/ColorPoints.h
Normal file
114
ui/map/elements/ColorPoints.h
Normal file
@@ -0,0 +1,114 @@
|
||||
#ifndef GL_PARTICLES_H
|
||||
#define GL_PARTICLES_H
|
||||
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include <KLib/math/filter/particles/ParticleFilter.h>
|
||||
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLPoints.h"
|
||||
#include "../Renderable.h"
|
||||
|
||||
#include "../../../nav/Node.h"
|
||||
|
||||
class ColorPoints : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
GLPoints points;
|
||||
float size = 3.0f;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
ColorPoints() {
|
||||
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
void setFromGridImportance(Grid<MyGridNode>* grid) {
|
||||
|
||||
points.clear();
|
||||
|
||||
for (const MyGridNode& n : *grid) {
|
||||
const QVector3D pt(n.x_cm/100.0f, n.z_cm/100.0f + 0.1f, n.y_cm/100.0f); // swap z and y
|
||||
const float f = n.getNavImportance();
|
||||
float h = 0.66 - (f*0.20); // 0.66 is blue on the HSV-scale
|
||||
if (h < 0) {h = 0;}
|
||||
if (h > 1) {h = 1;}
|
||||
const QColor color = QColor::fromHsvF(h, 1, 1);
|
||||
points.addPoint(pt, color);
|
||||
}
|
||||
|
||||
size = 3.0f;
|
||||
points.rebuild();
|
||||
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
template <typename T> void setFromParticles(const std::vector<K::Particle<T>>& particles) {
|
||||
|
||||
points.clear();
|
||||
|
||||
// group particles by grid-point
|
||||
std::unordered_map<GridPoint, float> weights;
|
||||
for (const K::Particle<T>& p : particles) {
|
||||
const GridPoint gp = p.state.position;
|
||||
weights[gp] += p.weight;
|
||||
}
|
||||
|
||||
// find min/max
|
||||
float min = +INFINITY;
|
||||
float max = -INFINITY;
|
||||
for (auto it : weights) {
|
||||
if (it.second > max) {max = it.second;}
|
||||
if (it.second < min) {min = it.second;}
|
||||
}
|
||||
|
||||
// draw colored
|
||||
for (auto it : weights) {
|
||||
const GridPoint gp = it.first;
|
||||
const float w = it.second;
|
||||
const float p = (w-min) / (max-min); // [0:1]
|
||||
const QVector3D pt(gp.x_cm/100.0f, gp.z_cm/100.0f + 0.1f, gp.y_cm/100.0f); // swap z and y
|
||||
float h = 0.66 - (p*0.66); // 0.66 is blue on the HSV-scale
|
||||
const QColor color = QColor::fromHsvF(h, 1, 1);
|
||||
points.addPoint(pt, color);
|
||||
}
|
||||
|
||||
|
||||
// for (const K::Particle<T>& p : particles) {
|
||||
// const GridPoint gp = p.state.position;
|
||||
// const QVector3D pt(gp.x_cm/100.0f, gp.z_cm/100.0f + 0.1f, gp.y_cm/100.0f); // swap z and y
|
||||
// const QColor color = Qt::blue;
|
||||
// points.addPoint(pt, color);
|
||||
// }
|
||||
|
||||
size = 6.0f;
|
||||
points.rebuild();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void initGL() override {
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentColorPoint.glsl");
|
||||
//program.setUniformValue("color", QVector4D(0.5, 0.5, 0.5, 1.0));
|
||||
points.initGL();
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
//glDisable(GL_DEPTH_TEST);
|
||||
//glPointSize()
|
||||
#ifndef ANDROID
|
||||
glPointSize(size);
|
||||
#endif
|
||||
points.render(&program);
|
||||
//glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // GL_PARTICLES_H
|
||||
101
ui/map/elements/Doors.h
Normal file
101
ui/map/elements/Doors.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef DOORS_H
|
||||
#define DOORS_H
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLTriangles.h"
|
||||
#include "../Renderable.h"
|
||||
|
||||
class Doors : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::Floor* floor;
|
||||
|
||||
GLTriangles<VertNormTexTan> doors;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Doors(Floorplan::Floor* floor) : floor(floor) {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
void initGL() override {
|
||||
|
||||
build();
|
||||
doors.setDiffuse(":/res/gl/tex/door2.jpg");
|
||||
doors.setNormalMap(":/res/gl/tex/door2_normal.jpg");
|
||||
|
||||
doors.build();
|
||||
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentTex.glsl");
|
||||
program.setUniformValue("texDiffuse", 0);
|
||||
program.setUniformValue("texNormalMap", 1);
|
||||
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
doors.render(&program);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
|
||||
for (Floorplan::FloorObstacle* obstacle : floor->obstacles) {
|
||||
|
||||
if (dynamic_cast<Floorplan::FloorObstacleDoor*>(obstacle)) {
|
||||
Floorplan::FloorObstacleDoor* door = dynamic_cast<Floorplan::FloorObstacleDoor*>(obstacle);
|
||||
addFace(door->from, door->to, floor->getStartingZ(), floor->getStartingZ() + door->height, door->swap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void addFace(const Point2 from, Point2 to, const float h1, const float h2, const bool swap) {
|
||||
|
||||
to = from + (to-from).rotated(60/180.0f*M_PI * ((swap)?(-1):(+1)) );
|
||||
|
||||
const QVector3D vert1(from.x, h1, from.y);
|
||||
const QVector3D vert2(to.x, h1, to.y);
|
||||
const QVector3D vert3(to.x, h2, to.y);
|
||||
const QVector3D vert4(from.x, h2, from.y);
|
||||
|
||||
const QVector3D n1 = GLHelper::getNormal(vert1, vert2, vert3);
|
||||
const QVector3D n2 = -n1;
|
||||
|
||||
QVector3D tan = (vert1-vert2).normalized();
|
||||
tan = GLHelper::isCCW(vert1, vert2, vert3) ? (tan) : (-tan);
|
||||
|
||||
const QVector2D tex1(0, 0);
|
||||
const QVector2D tex2(1, 0);
|
||||
const QVector2D tex3(1, 1);
|
||||
const QVector2D tex4(0, 1);
|
||||
|
||||
{
|
||||
const VertNormTexTan vnt1(vert1, n1, tex1, tan);
|
||||
const VertNormTexTan vnt2(vert2, n1, tex2, tan);
|
||||
const VertNormTexTan vnt3(vert3, n1, tex3, tan);
|
||||
const VertNormTexTan vnt4(vert4, n1, tex4, tan);
|
||||
doors.addQuadCCW(vnt1, vnt2, vnt3, vnt4);
|
||||
} {
|
||||
const VertNormTexTan vnt1(vert1, n2, tex1, -tan);
|
||||
const VertNormTexTan vnt2(vert2, n2, tex2, -tan);
|
||||
const VertNormTexTan vnt3(vert3, n2, tex3, -tan);
|
||||
const VertNormTexTan vnt4(vert4, n2, tex4, -tan);
|
||||
doors.addQuadCW(vnt1, vnt2, vnt3, vnt4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // DOORS_H
|
||||
156
ui/map/elements/Ground.h
Normal file
156
ui/map/elements/Ground.h
Normal file
@@ -0,0 +1,156 @@
|
||||
#ifndef GROUND_H
|
||||
#define GROUND_H
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLTriangles.h"
|
||||
#include "../gl/GLLines.h"
|
||||
#include "../Renderable.h"
|
||||
|
||||
#include "../../../lib/gpc/Polygon.h"
|
||||
|
||||
class Ground : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::Floor* floor;
|
||||
|
||||
GLTriangles<VertNormTexTan> flooring;
|
||||
GLTriangles<VertNormTexTan> ceiling;
|
||||
|
||||
GLLines outline;
|
||||
bool outlineOnly = false;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Ground(Floorplan::Floor* floor) : floor(floor) {
|
||||
setOutlineOnly(false);
|
||||
}
|
||||
|
||||
|
||||
void initGL() override {
|
||||
|
||||
build();
|
||||
|
||||
flooring.setDiffuse(":/res/gl/tex/floor4.jpg");
|
||||
flooring.setNormalMap(":/res/gl/tex/floor4_normal.jpg");
|
||||
|
||||
ceiling.setDiffuse(":/res/gl/tex/floor4.jpg");
|
||||
ceiling.setNormalMap(":/res/gl/tex/floor4_normal.jpg");
|
||||
|
||||
flooring.build();
|
||||
ceiling.build();
|
||||
outline.build();
|
||||
|
||||
//loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentTex.glsl");
|
||||
//program.setUniformValue("texDiffuse", 0);
|
||||
//program.setUniformValue("texNormalMap", 1);
|
||||
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
if (outlineOnly) {
|
||||
glLineWidth(5);
|
||||
outline.render(&program);
|
||||
} else {
|
||||
flooring.render(&program);
|
||||
ceiling.render(&program);
|
||||
}
|
||||
}
|
||||
|
||||
/** render only the outline? */
|
||||
void setOutlineOnly(const bool outline) override {
|
||||
// this->outlineOnly = outline;
|
||||
// if (outlineOnly) {
|
||||
// loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentLine.glsl");
|
||||
// program.setUniformValue("color", QVector4D(0.0, 0.0, 0.4, 1.0));
|
||||
// } else {
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentTex.glsl");
|
||||
program.setUniformValue("texDiffuse", 0);
|
||||
program.setUniformValue("texNormalMap", 1);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
|
||||
std::vector<gpc_polygon> add;
|
||||
std::vector<gpc_polygon> rem;
|
||||
|
||||
const std::vector<Floorplan::FloorOutlinePolygon*>& polys = floor->outline;
|
||||
|
||||
Polygon pol;
|
||||
|
||||
for (Floorplan::FloorOutlinePolygon* poly : polys) {
|
||||
switch (poly->method) {
|
||||
case Floorplan::OutlineMethod::ADD: pol.add(poly->poly); break;
|
||||
case Floorplan::OutlineMethod::REMOVE: pol.remove(poly->poly); break;
|
||||
default: throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<Point3>> trias = pol.get(floor->atHeight);
|
||||
|
||||
|
||||
// is stored as TRIANGLE_STRIP
|
||||
// => triangle might have more than 3 points
|
||||
for (const std::vector<Point3>& tria : trias) {
|
||||
|
||||
|
||||
const QVector3D normFloor(0, +1, 0);
|
||||
const QVector3D normCeil(0, -1, 0);
|
||||
const QVector3D t(1,0,0);
|
||||
|
||||
const float s = 0.6;
|
||||
|
||||
// add vertices
|
||||
for (int i = 2; i < (int) tria.size(); i+=1) {
|
||||
|
||||
const Point3 p1 = tria[i-2];
|
||||
const Point3 p2 = tria[i-1];
|
||||
const Point3 p3 = tria[i-0];
|
||||
|
||||
const QVector3D vert1(p1.x, p1.z, p1.y);
|
||||
const QVector3D vert2(p2.x, p2.z, p2.y);
|
||||
const QVector3D vert3(p3.x, p3.z, p3.y);
|
||||
|
||||
{
|
||||
const VertNormTexTan vnt1(vert1, normFloor, tex(vert1*s), t);
|
||||
const VertNormTexTan vnt2(vert2, normFloor, tex(vert2*s), t);
|
||||
const VertNormTexTan vnt3(vert3, normFloor, tex(vert3*s), t);
|
||||
flooring.addFaceCCW(vnt1, vnt2, vnt3);
|
||||
} {
|
||||
const VertNormTexTan vnt1(vert1, normCeil, tex(vert1*s), t);
|
||||
const VertNormTexTan vnt2(vert2, normCeil, tex(vert2*s), t);
|
||||
const VertNormTexTan vnt3(vert3, normCeil, tex(vert3*s), t);
|
||||
ceiling.addFaceCW(vnt1, vnt2, vnt3);
|
||||
}
|
||||
|
||||
outline.addLine(vert1, vert2);
|
||||
outline.addLine(vert2, vert3);
|
||||
outline.addLine(vert3, vert1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
QVector2D tex(const QVector3D vert) {
|
||||
return QVector2D(vert.x(), vert.z());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // GROUND_H
|
||||
89
ui/map/elements/Handrails.h
Normal file
89
ui/map/elements/Handrails.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef HANDRAIL_H
|
||||
#define HANDRAIL_H
|
||||
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLLines.h"
|
||||
#include "../Renderable.h"
|
||||
|
||||
|
||||
class Handrails : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::Floor* floor;
|
||||
|
||||
GLLines lines;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Handrails(Floorplan::Floor* floor) : floor(floor) {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
void initGL() override {
|
||||
build();
|
||||
lines.build();
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentLine.glsl");
|
||||
program.setUniformValue("color", QVector4D(0.5, 0.5, 0.5, 1.0));
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
glLineWidth(2);
|
||||
lines.render(&program);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
|
||||
for (Floorplan::FloorObstacle* obstacle : floor->obstacles) {
|
||||
|
||||
if (dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle)) {
|
||||
Floorplan::FloorObstacleLine* line = dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle);
|
||||
if (line->type != Floorplan::ObstacleType::HANDRAIL) {continue;}
|
||||
add(line->from, line->to, floor->getStartingZ());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void add(const Point2 from, const Point2 to, const float h1) {
|
||||
|
||||
// handrail height
|
||||
const float h2 = h1 + 0.8;
|
||||
|
||||
const QVector3D v1(to.x, h2, to.y);
|
||||
const QVector3D v2(from.x, h2, from.y);
|
||||
|
||||
// upper
|
||||
lines.addLine(v1, v2);
|
||||
|
||||
const float stepSize = 0.5;
|
||||
const float len = from.getDistance(to);
|
||||
const float steps = std::round(len / stepSize);
|
||||
|
||||
for (int i = 0; i <= steps; ++i) {
|
||||
const float percent = (float) i / (float) steps;
|
||||
const Point2 pos = from + (to-from) * percent;
|
||||
const QVector3D v1(pos.x, h1, pos.y);
|
||||
const QVector3D v2(pos.x, h2, pos.y);
|
||||
lines.addLine(v1, v2);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // HANDRAIL_H
|
||||
84
ui/map/elements/Object.h
Normal file
84
ui/map/elements/Object.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#ifndef OBJECT_H
|
||||
#define OBJECT_H
|
||||
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLTriangles.h"
|
||||
#include "../Renderable.h"
|
||||
|
||||
#include <KLib/data/obj/ObjectFile.h>
|
||||
|
||||
|
||||
class Object : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
GLTriangles<VertNormTex> triangles;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Object(const std::string& file, const std::string& colorTexture, std::string normalsTexture, const float scale = 1.0) {
|
||||
|
||||
K::ObjFileReader reader(file, false);
|
||||
|
||||
|
||||
if (normalsTexture.empty()) {normalsTexture = ":/res/gl/tex/empty_normals.jpg";}
|
||||
|
||||
triangles.setDiffuse(colorTexture.c_str());
|
||||
triangles.setNormalMap(normalsTexture.c_str());
|
||||
|
||||
for (const K::ObjFileReader::Face& face : reader.getData().faces) {
|
||||
|
||||
const QVector3D vertex1(face.vnt[0].vertex.x, face.vnt[0].vertex.y, face.vnt[0].vertex.z);
|
||||
const QVector3D vertex2(face.vnt[1].vertex.x, face.vnt[1].vertex.y, face.vnt[1].vertex.z);
|
||||
const QVector3D vertex3(face.vnt[2].vertex.x, face.vnt[2].vertex.y, face.vnt[2].vertex.z);
|
||||
|
||||
const QVector3D normal1(face.vnt[0].normal.x, face.vnt[0].normal.y, face.vnt[0].normal.z);
|
||||
const QVector3D normal2(face.vnt[1].normal.x, face.vnt[1].normal.y, face.vnt[1].normal.z);
|
||||
const QVector3D normal3(face.vnt[2].normal.x, face.vnt[2].normal.y, face.vnt[2].normal.z);
|
||||
|
||||
const QVector2D texture1(face.vnt[0].texture.x, face.vnt[0].texture.y);
|
||||
const QVector2D texture2(face.vnt[1].texture.x, face.vnt[1].texture.y);
|
||||
const QVector2D texture3(face.vnt[2].texture.x, face.vnt[2].texture.y);
|
||||
|
||||
const QVector3D o(0, 0.0, 0);
|
||||
|
||||
const VertNormTex vnt1(vertex1*scale+o, normal1, texture1);
|
||||
const VertNormTex vnt2(vertex2*scale+o, normal2, texture2);
|
||||
const VertNormTex vnt3(vertex3*scale+o, normal3, texture3);
|
||||
|
||||
triangles.addFace(vnt1, vnt2, vnt3);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void initGL() override {
|
||||
build();
|
||||
triangles.build();
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentTex.glsl");
|
||||
program.setUniformValue("texDiffuse", 0);
|
||||
program.setUniformValue("texNormalMap", 1);
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
triangles.render(&program);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
triangles.build();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // OBJECT_H
|
||||
317
ui/map/elements/Path.h
Normal file
317
ui/map/elements/Path.h
Normal file
@@ -0,0 +1,317 @@
|
||||
#ifndef PATH_H
|
||||
#define PATH_H
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include <Indoor/nav/dijkstra/DijkstraPath.h>
|
||||
|
||||
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLTriangles.h"
|
||||
#include "../Renderable.h"
|
||||
|
||||
|
||||
class Path : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
GLTriangles<VertNormTex> lines;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Path() {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
void initGL() override {
|
||||
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentLine.glsl");
|
||||
program.setUniformValue("color", QVector4D(0.0, 0.4, 1.0, 0.6));
|
||||
|
||||
//loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentTexSimple.glsl");
|
||||
|
||||
lines.setDiffuse(":/res/gl/tex/arrows.png");
|
||||
program.setUniformValue("texNormalMap", 0);
|
||||
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
lines.rebuild();
|
||||
glLineWidth(30);
|
||||
glEnable(GL_BLEND);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
lines.render(&program);
|
||||
glDisable(GL_BLEND);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
}
|
||||
|
||||
|
||||
template <typename Node> void set(const DijkstraPath<Node>& path) {
|
||||
|
||||
std::vector<Point3> out;
|
||||
for (const DijkstraNode<Node>* node : path.getVector()) {
|
||||
if (!node) {break;}
|
||||
const Node* elem = node->element;
|
||||
out.push_back(Point3(elem->x_cm/100.0f, elem->y_cm/100.0f, elem->z_cm/100.0f));
|
||||
}
|
||||
|
||||
out = simplify(out);
|
||||
set(out);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
void setSimple(const std::vector<Point3>& path) {
|
||||
|
||||
|
||||
lines.clear();
|
||||
const float s = 0.5;
|
||||
|
||||
Point3 lastDir(0,0,0);
|
||||
std::vector<Floorplan::Quad3> quads;
|
||||
|
||||
for (int i = 1; i < (int) path.size(); ++i) {
|
||||
|
||||
Point3 pa = path[i-1];
|
||||
Point3 pb = path[i-0];
|
||||
const Point3 pc(0, 0, 1);
|
||||
|
||||
Point3 dir = pb - pa; dir /= dir.length();
|
||||
Point3 perb = cross(pa-pb, pc); perb /= perb.length();
|
||||
|
||||
const Point3 p1 = pa - perb*s;
|
||||
const Point3 p2 = pa + perb*s;
|
||||
const Point3 p3 = pb + perb*s;
|
||||
const Point3 p4 = pb - perb*s;
|
||||
|
||||
if (dir == lastDir) {
|
||||
quads.back().p3 = p3;
|
||||
quads.back().p4 = p4;
|
||||
} else {
|
||||
quads.push_back(Floorplan::Quad3(p1,p2,p3,p4));
|
||||
}
|
||||
|
||||
lastDir = dir;
|
||||
|
||||
// // produce a small gap between path-lines [will be filled with another quad!]
|
||||
// pa += dir * 0.6;
|
||||
// pb -= dir * 0.6;
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int) quads.size(); ++i) {
|
||||
|
||||
// add the line-segment
|
||||
const Floorplan::Quad3 q1 = quads[i];
|
||||
addQuad(q1);
|
||||
|
||||
// // construct the quad between adjacent segments
|
||||
// if (i < (int) quads.size() - 1) {
|
||||
// const Floorplan::Quad3 q2 = quads[i+1];
|
||||
// const Floorplan::Quad3 q3(q1.p3, q2.p2, q2.p1, q1.p4);
|
||||
// addQuad(q3);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
/** combine nodes while the direction stays the same (many small quads -> one large quad) */
|
||||
std::vector<Point3> simplify(const std::vector<Point3>& path) {
|
||||
|
||||
std::vector<Point3> out;
|
||||
|
||||
Point3 lastDir(0,0,0);
|
||||
if (!path.empty()) {
|
||||
out.push_back(path.back());
|
||||
}
|
||||
|
||||
for (int i = path.size() - 1; i >= 1; --i) {
|
||||
|
||||
const Point3 pa = path[i-0];
|
||||
const Point3 pb = path[i-1];
|
||||
const Point3 dir = (pb - pa).normalized();
|
||||
|
||||
if (dir == lastDir) {
|
||||
out[out.size()-1] = pb;
|
||||
} else {
|
||||
out.push_back(pb);
|
||||
}
|
||||
|
||||
lastDir = dir;
|
||||
|
||||
}
|
||||
|
||||
// remove unneccesary nodes
|
||||
for (int i = 1; i < (int) out.size() - 1; ++i) {
|
||||
|
||||
const Point3 pa = out[i-1];
|
||||
const Point3 pb = out[i-0];
|
||||
const Point3 pc = out[i+1];
|
||||
|
||||
const float min = 0.6;
|
||||
const float d1 = pb.getDistance(pa);
|
||||
const float d2 = pb.getDistance(pc);
|
||||
|
||||
if (d1 < min || d2 < min) {
|
||||
out.erase(out.begin() + i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
// void set(const std::vector<Point3>& path) {
|
||||
|
||||
// lines.clear();
|
||||
|
||||
// const float s = 0.4;
|
||||
// std::vector<Point3> pts;
|
||||
|
||||
// for (int i = 0; i < (int) path.size(); ++i) {
|
||||
|
||||
// const Point3 pa = path[i-1];
|
||||
// const Point3 pb = path[i-0];
|
||||
// const Point3 pc(0, 0, 1);
|
||||
|
||||
// const Point3 perb = cross(pa-pb, pc).normalized();
|
||||
|
||||
// // quad's edges
|
||||
// const Point3 p1 = pa - perb*s;
|
||||
// const Point3 p2 = pa + perb*s;
|
||||
// const Point3 p3 = pb + perb*s;
|
||||
// const Point3 p4 = pb - perb*s;
|
||||
|
||||
// pts.push_back(p1);
|
||||
// pts.push_back(p2);
|
||||
|
||||
// }
|
||||
|
||||
// std::vector<Floorplan::Quad3> quads;
|
||||
// for (int i = 0; i < (int) pts.size(); i+=2) {
|
||||
// quads.push_back(Floorplan::Quad3(pts[i+0], pts[i+1], pts[i+3], pts[i+2]));
|
||||
// }
|
||||
|
||||
// float l1 = 0;
|
||||
// float l2 = 0;
|
||||
|
||||
// for (int i = 0; i < (int) quads.size(); ++i) {
|
||||
|
||||
// // add the line-segment
|
||||
// const Floorplan::Quad3 q1 = quads[i];
|
||||
// l2 += ((q1.p1 + q1.p2) / 2).getDistance( (q1.p3 + q1.p4) / 2 );
|
||||
// addQuad(q1, l1, l2);
|
||||
// l1 = l2;
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
void set(const std::vector<Point3>& path) {
|
||||
|
||||
|
||||
lines.clear();
|
||||
|
||||
// half the width of the path
|
||||
const float s = 0.4;
|
||||
std::vector<Floorplan::Quad3> quads;
|
||||
|
||||
for (int i = 1; i < (int) path.size(); ++i) {
|
||||
|
||||
Point3 pa = path[i-1];
|
||||
Point3 pb = path[i-0];
|
||||
const Point3 pc(0, 0, 1);
|
||||
Point3 dir = (pb - pa).normalized();
|
||||
|
||||
// produce a small gap between path-segments
|
||||
// those segments will be smoothly connected using another quad
|
||||
pa += dir * 0.35;
|
||||
pb -= dir * 0.35;
|
||||
|
||||
const Point3 perb = cross(pa-pb, pc).normalized();
|
||||
|
||||
// quad's edges
|
||||
const Point3 p1 = pa - perb*s;
|
||||
const Point3 p2 = pa + perb*s;
|
||||
const Point3 p3 = pb + perb*s;
|
||||
const Point3 p4 = pb - perb*s;
|
||||
|
||||
// add
|
||||
quads.push_back(Floorplan::Quad3(p1,p2,p3,p4));
|
||||
|
||||
}
|
||||
|
||||
float l1 = 0;
|
||||
float l2 = 0;
|
||||
|
||||
for (int i = 0; i < (int) quads.size(); ++i) {
|
||||
|
||||
// add the line-segment
|
||||
const Floorplan::Quad3 q1 = quads[i];
|
||||
l2 += ((q1.p1 + q1.p2) / 2).getDistance( (q1.p3 + q1.p4) / 2 );
|
||||
addQuad(q1, l1, l2);
|
||||
l1 = l2;
|
||||
|
||||
// done?
|
||||
if (i == (int) quads.size() - 1) {break;}
|
||||
|
||||
// construct the quad between adjacent segments
|
||||
const Floorplan::Quad3 q2 = quads[i+1];
|
||||
const Floorplan::Quad3 q3(q1.p4, q1.p3, q2.p2, q2.p1);
|
||||
l2 += ((q3.p1 + q3.p2) / 2).getDistance( (q3.p3 + q3.p4) / 2 );
|
||||
addQuad(q3, l1, l2);
|
||||
l1 = l2;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void addQuad(const Floorplan::Quad3& q, const float l1, const float l2) {
|
||||
|
||||
// move the path upwards (slightly above the ground)
|
||||
const float h = 0.40;
|
||||
|
||||
const QVector3D v1(q.p1.x, q.p1.z+h, q.p1.y);
|
||||
const QVector3D v2(q.p2.x, q.p2.z+h, q.p2.y);
|
||||
const QVector3D v3(q.p3.x, q.p3.z+h, q.p3.y);
|
||||
const QVector3D v4(q.p4.x, q.p4.z+h, q.p4.y);
|
||||
|
||||
const QVector3D n(0,1,0);
|
||||
|
||||
const QVector2D tex1(0, l1);
|
||||
const QVector2D tex2(1, l1);
|
||||
const QVector2D tex3(1, l2);
|
||||
const QVector2D tex4(0, l2);
|
||||
|
||||
// const QVector2D tex1(q.p1.x, q.p1.y);
|
||||
// const QVector2D tex2(q.p2.x, q.p2.y);
|
||||
// const QVector2D tex3(q.p3.x, q.p3.y);
|
||||
// const QVector2D tex4(q.p4.x, q.p4.y);
|
||||
|
||||
const VertNormTex vnt1(v1, n, tex1);
|
||||
const VertNormTex vnt2(v2, n, tex2);
|
||||
const VertNormTex vnt3(v3, n, tex3);
|
||||
const VertNormTex vnt4(v4, n, tex4);
|
||||
|
||||
|
||||
lines.addQuadCCW(vnt1, vnt2, vnt3, vnt4);
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // PATH_H
|
||||
153
ui/map/elements/Stairs.h
Normal file
153
ui/map/elements/Stairs.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#ifndef STAIRS_H
|
||||
#define STAIRS_H
|
||||
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLTriangles.h"
|
||||
#include "../Renderable.h"
|
||||
|
||||
|
||||
class Stairs : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::Floor* floor;
|
||||
|
||||
GLTriangles<VertNormTex> parts;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Stairs(Floorplan::Floor* floor) : floor(floor) {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
void initGL() override {
|
||||
|
||||
build();
|
||||
// parts.setDiffuse(":/res/gl/tex/granite1.jpg");
|
||||
// parts.setNormalMap(":/res/gl/tex/granite1_normal.jpg");
|
||||
parts.setDiffuse(":/res/gl/tex/floor4.jpg");
|
||||
parts.setNormalMap(":/res/gl/tex/floor4_normal.jpg");
|
||||
parts.build();
|
||||
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentTex.glsl");
|
||||
program.setUniformValue("texDiffuse", 0);
|
||||
program.setUniformValue("texNormalMap", 1);
|
||||
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
parts.render(&program);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
|
||||
for (Floorplan::Stair* stair : floor->stairs) {
|
||||
|
||||
if (dynamic_cast<Floorplan::Stair*>(stair)) {
|
||||
Floorplan::StairFreeform* freeform = dynamic_cast<Floorplan::StairFreeform*>(stair);
|
||||
add(Floorplan::getQuads(freeform->getParts(), floor));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void add(const std::vector<Floorplan::Quad3>& quads) {
|
||||
|
||||
for (const Floorplan::Quad3& quad : quads) {
|
||||
|
||||
//void addQuad(quad);
|
||||
|
||||
stepify(quad);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void stepify(const Floorplan::Quad3& quad) {
|
||||
|
||||
const float len = (quad.p4 - quad.p1).length();
|
||||
const float stepLen = 0.3;
|
||||
const int steps = std::round(len / stepLen);
|
||||
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
const float per1 = (float) (i+0) / (float) steps;
|
||||
const float per2 = (float) (i+1) / (float) steps;
|
||||
|
||||
const Point3 p1 = quad.p1 + (quad.p4 - quad.p1) * per1;
|
||||
const Point3 p2 = quad.p2 + (quad.p3 - quad.p2) * per1;
|
||||
const Point3 p5 = quad.p2 + (quad.p3 - quad.p2) * per2;
|
||||
const Point3 p6 = quad.p1 + (quad.p4 - quad.p1) * per2;
|
||||
Point3 p3 = p5; p3.z = p2.z;
|
||||
Point3 p4 = p6; p4.z = p1.z;
|
||||
|
||||
|
||||
addQuad(Floorplan::Quad3(p1, p2, p3, p4));
|
||||
addQuad(Floorplan::Quad3(p3, p4, p6, p5));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void addQuad(const Floorplan::Quad3& quad) {
|
||||
|
||||
const QVector3D vert1(quad.p1.x, quad.p1.z, quad.p1.y);
|
||||
const QVector3D vert2(quad.p2.x, quad.p2.z, quad.p2.y);
|
||||
const QVector3D vert3(quad.p3.x, quad.p3.z, quad.p3.y);
|
||||
const QVector3D vert4(quad.p4.x, quad.p4.z, quad.p4.y);
|
||||
|
||||
const QVector3D n1 = GLHelper::getNormal(vert1, vert2, vert3);
|
||||
const QVector3D n2 = -n1;
|
||||
|
||||
// const float o =
|
||||
|
||||
// const QVector2D tex1(quad.p1.length(), quad.p1.y);
|
||||
// const QVector2D tex2(quad.p2.x, quad.p2.y+quad.p2.z);
|
||||
// const QVector2D tex3(quad.p3.x, quad.p3.y+quad.p3.z);
|
||||
// const QVector2D tex4(quad.p4.x, quad.p4.y+quad.p4.z);
|
||||
|
||||
const float h = quad.p4.getDistance(quad.p1);
|
||||
const float l = quad.p1.getDistance(quad.p2);
|
||||
const float o = quad.p1.length();
|
||||
const float s = 1.1; // 0.5;
|
||||
|
||||
const QVector2D tex1(o+0, h); // start texturing at the ceiling so above-door-sections and walls have the same textre
|
||||
const QVector2D tex2(o+l, h);
|
||||
const QVector2D tex3(o+l, 0);
|
||||
const QVector2D tex4(o+0, 0);
|
||||
|
||||
// const VertNormTex vnt1(vert1, n1, tex1*s);
|
||||
// const VertNormTex vnt2(vert2, n1, tex2*s);
|
||||
// const VertNormTex vnt3(vert3, n1, tex3*s);
|
||||
// const VertNormTex vnt4(vert4, n1, tex4*s);
|
||||
|
||||
{
|
||||
const VertNormTex vnt1(vert1, n1, tex1*s);
|
||||
const VertNormTex vnt2(vert2, n1, tex2*s);
|
||||
const VertNormTex vnt3(vert3, n1, tex3*s);
|
||||
const VertNormTex vnt4(vert4, n1, tex4*s);
|
||||
parts.addQuadCCW(vnt1, vnt2, vnt3, vnt4);
|
||||
} {
|
||||
const VertNormTex vnt1(vert1, n2, tex1*s);
|
||||
const VertNormTex vnt2(vert2, n2, tex2*s);
|
||||
const VertNormTex vnt3(vert3, n2, tex3*s);
|
||||
const VertNormTex vnt4(vert4, n2, tex4*s);
|
||||
parts.addQuadCW(vnt1, vnt2, vnt3, vnt4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // STAIRS_H
|
||||
144
ui/map/elements/Walls.h
Normal file
144
ui/map/elements/Walls.h
Normal file
@@ -0,0 +1,144 @@
|
||||
#ifndef WALLS_H
|
||||
#define WALLS_H
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "../gl/GLHelper.h"
|
||||
#include "../gl/GLTriangles.h"
|
||||
#include "../gl/GLLines.h"
|
||||
#include "../Renderable.h"
|
||||
#include "../gl/Shader.h"
|
||||
|
||||
|
||||
class Walls : public Renderable {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::Floor* floor;
|
||||
|
||||
GLTriangles<VertNormTexTan> triangles;
|
||||
GLLines outlines;
|
||||
bool outlineOnly = false;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Walls(Floorplan::Floor* floor) : floor(floor) {
|
||||
|
||||
setOutlineOnly(false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void initGL() override {
|
||||
|
||||
build();
|
||||
|
||||
triangles.build();
|
||||
triangles.setDiffuse(":/res/gl/tex/wall3.jpg");
|
||||
triangles.setNormalMap(":/res/gl/tex/wall3_normal.jpg");
|
||||
|
||||
outlines.build();
|
||||
|
||||
}
|
||||
|
||||
/** render the floor */
|
||||
void _render() override {
|
||||
if (outlineOnly) {
|
||||
glLineWidth(1);
|
||||
outlines.render(&program);
|
||||
} else {
|
||||
triangles.render(&program);
|
||||
}
|
||||
}
|
||||
|
||||
/** render only the outline? */
|
||||
void setOutlineOnly(const bool outline) override {
|
||||
this->outlineOnly = outline;
|
||||
if (outlineOnly) {
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentLine.glsl");
|
||||
program.setUniformValue("color", QVector4D(0.9, 0.9, 0.9, 1.0));
|
||||
} else {
|
||||
loadShader(":/res/gl/vertex1.glsl", ":/res/gl/fragmentTex.glsl");
|
||||
program.setUniformValue("texDiffuse", 0);
|
||||
program.setUniformValue("texNormalMap", 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
|
||||
for (Floorplan::FloorObstacle* obstacle : floor->obstacles) {
|
||||
|
||||
if (dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle)) {
|
||||
Floorplan::FloorObstacleLine* line = dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle);
|
||||
if (line->type != Floorplan::ObstacleType::WALL) {continue;}
|
||||
addFace(line->from, line->to, floor->getStartingZ(), floor->getEndingZ());
|
||||
} else if (dynamic_cast<Floorplan::FloorObstacleDoor*>(obstacle)) {
|
||||
Floorplan::FloorObstacleDoor* door = dynamic_cast<Floorplan::FloorObstacleDoor*>(obstacle);
|
||||
addFace(door->from, door->to, floor->getStartingZ() + door->height, floor->getEndingZ());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void addFace(const Point2 from, const Point2 to, const float h1, const float h2) {
|
||||
|
||||
const QVector3D vert1(from.x, h1, from.y);
|
||||
const QVector3D vert2(to.x, h1, to.y);
|
||||
const QVector3D vert3(to.x, h2, to.y);
|
||||
const QVector3D vert4(from.x, h2, from.y);
|
||||
|
||||
const QVector3D n1 = GLHelper::getNormal(vert1, vert2, vert3);
|
||||
const QVector3D n2 = -n1;
|
||||
|
||||
|
||||
|
||||
QVector3D tan = (vert1-vert2).normalized();
|
||||
tan = GLHelper::isCCW(vert1, vert2, vert3) ? (tan) : (-tan);
|
||||
|
||||
const float l = from.getDistance(to);
|
||||
const float h = h2-h1;
|
||||
const float o = std::min(from.length(), to.length());
|
||||
|
||||
const QVector2D tex1(o+0, h); // start texturing at the ceiling so above-door-sections and walls have the same textre
|
||||
const QVector2D tex2(o+l, h);
|
||||
const QVector2D tex3(o+l, 0);
|
||||
const QVector2D tex4(o+0, 0);
|
||||
|
||||
const float s = 0.65;
|
||||
|
||||
|
||||
|
||||
{
|
||||
const VertNormTexTan vnt1(vert1, n1, tex1*s, tan);
|
||||
const VertNormTexTan vnt2(vert2, n1, tex2*s, tan);
|
||||
const VertNormTexTan vnt3(vert3, n1, tex3*s, tan);
|
||||
const VertNormTexTan vnt4(vert4, n1, tex4*s, tan);
|
||||
triangles.addQuadCCW(vnt1, vnt2, vnt3, vnt4);
|
||||
} {
|
||||
const VertNormTexTan vnt1(vert1, n2, tex1*s, -tan);
|
||||
const VertNormTexTan vnt2(vert2, n2, tex2*s, -tan);
|
||||
const VertNormTexTan vnt3(vert3, n2, tex3*s, -tan);
|
||||
const VertNormTexTan vnt4(vert4, n2, tex4*s, -tan);
|
||||
triangles.addQuadCW(vnt1, vnt2, vnt3, vnt4);
|
||||
}
|
||||
|
||||
outlines.addLine(vert1, vert2);
|
||||
outlines.addLine(vert2, vert3);
|
||||
outlines.addLine(vert3, vert4);
|
||||
outlines.addLine(vert4, vert1);
|
||||
|
||||
}
|
||||
|
||||
//private:
|
||||
//
|
||||
// QVector2D tex(const QVector3D vert) {
|
||||
// return QVector2D(vert.x(), vert.y());
|
||||
// }
|
||||
|
||||
};
|
||||
|
||||
#endif // WALLS_H
|
||||
67
ui/map/gl/GL.h
Normal file
67
ui/map/gl/GL.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef HELPER_GL_H
|
||||
#define HELPER_GL_H
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QOpenGLTexture>
|
||||
|
||||
struct Vert {
|
||||
QVector3D vert;
|
||||
Vert(QVector3D vert) : vert(vert) {;}
|
||||
int getVertOffset() const {return 0;}
|
||||
bool operator == (const Vert& o) const {return (vert == o.vert);}
|
||||
};
|
||||
|
||||
struct VertColor {
|
||||
QVector3D vert;
|
||||
QVector3D color;
|
||||
VertColor(QVector3D vert, QVector3D color) : vert(vert), color(color) {;}
|
||||
int getVertOffset() const {return 0;}
|
||||
int getColorOffset() const {return sizeof(QVector3D);}
|
||||
int getTanOffset() const {throw "error";}
|
||||
bool operator == (const VertColor& o) const {return (vert == o.vert) && (color == o.color);}
|
||||
static bool hasTangent() {return false;}
|
||||
static bool hasColor() {return true;}
|
||||
};
|
||||
|
||||
struct VertNorm {
|
||||
QVector3D vert;
|
||||
QVector3D norm;
|
||||
VertNorm(QVector3D vert, QVector3D norm) : vert(vert), norm(norm) {;}
|
||||
int getVertOffset() const {return 0;}
|
||||
int getNormOffset() const {return sizeof(QVector3D);}
|
||||
int getTanOffset() const {throw "error";}
|
||||
bool operator == (const VertNorm& o) const {return (vert == o.vert) && (norm == o.norm);}
|
||||
static bool hasTangent() {return false;}
|
||||
|
||||
};
|
||||
|
||||
struct VertNormTex {
|
||||
QVector3D vert;
|
||||
QVector3D norm;
|
||||
QVector2D tex;
|
||||
VertNormTex(QVector3D vert, QVector3D norm, QVector3D tex) : vert(vert), norm(norm), tex(tex) {;}
|
||||
int getVertOffset() const {return 0;}
|
||||
int getNormOffset() const {return sizeof(QVector3D);}
|
||||
int getTexOffset() const {return sizeof(QVector3D)*2;}
|
||||
int getTanOffset() const {throw "error";}
|
||||
bool operator == (const VertNormTex& o) const {return (vert == o.vert) && (norm == o.norm) && (tex == o.tex);}
|
||||
static bool hasTangent() {return false;}
|
||||
};
|
||||
|
||||
struct VertNormTexTan {
|
||||
QVector3D vert;
|
||||
QVector3D norm;
|
||||
QVector2D tex;
|
||||
QVector3D tan;
|
||||
VertNormTexTan(QVector3D vert, QVector3D norm, QVector3D tex, QVector3D tan) : vert(vert), norm(norm), tex(tex), tan(tan) {;}
|
||||
int getVertOffset() const {return 0;}
|
||||
int getNormOffset() const {return sizeof(QVector3D);}
|
||||
int getTexOffset() const {return sizeof(QVector3D)*2;}
|
||||
int getTanOffset() const {return sizeof(QVector3D)*2 + sizeof(QVector2D);}
|
||||
bool operator == (const VertNormTexTan& o) const {return (vert == o.vert) && (norm == o.norm) && (tex == o.tex) && (tan == o.tan);}
|
||||
static bool hasTangent() {return true;}
|
||||
};
|
||||
|
||||
#endif // HELPER_GL_H
|
||||
67
ui/map/gl/GLHelper.h
Normal file
67
ui/map/gl/GLHelper.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef MAP_HELPER_H
|
||||
#define MAP_HELPER_H
|
||||
|
||||
#include <Indoor/geo/Point3.h>
|
||||
#include <QOpenGLFunctions>
|
||||
|
||||
class GLHelper {
|
||||
|
||||
public:
|
||||
|
||||
static QVector3D getNormal(const QVector3D& v1, const QVector3D& v2, const QVector3D& v3) {
|
||||
|
||||
// get two of the triangle's edges
|
||||
const QVector4D v21 = v2-v1;
|
||||
const QVector4D v31 = v3-v1;
|
||||
|
||||
const QVector3D n = QVector3D::crossProduct(v21.toVector3D(), v31.toVector3D()).normalized();
|
||||
|
||||
return isCCW(v1, v2, v3) ? (n) : (-n);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* is the triangle given by p1,p2,p3 CCW?
|
||||
* NOTE: uses OUR coordinate system (x,y,z) where z is the floor-height
|
||||
*/
|
||||
static bool isCCW(const Point3 p1, const Point3 p2, const Point3 p3) {
|
||||
const QVector3D v1(p1.x, p1.z, p1.y);
|
||||
const QVector3D v2(p2.x, p2.z, p2.y);
|
||||
const QVector3D v3(p3.x, p3.z, p3.y);
|
||||
return isCCW(v1, v2, v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* is the triangle given by p1,p2,p3 CCW?
|
||||
* NOTE: uses OpenGL coordinate system (x,z,y) (our z is the floor-height)
|
||||
*/
|
||||
static bool isCCW(const QVector3D& p1, const QVector3D& p2, const QVector3D& p3) {
|
||||
|
||||
// camera position
|
||||
QMatrix4x4 proj; proj.lookAt(QVector3D(-1,20,-1), QVector3D(0,0,0), QVector3D(0,1,0));
|
||||
|
||||
// to camera space
|
||||
QVector4D v1(p1.x(), p1.y(), p1.z(), 1);
|
||||
QVector4D v2(p2.x(), p2.y(), p2.z(), 1);
|
||||
QVector4D v3(p3.x(), p3.y(), p3.z(), 1);
|
||||
v1 = proj * v1;
|
||||
v2 = proj * v2;
|
||||
v3 = proj * v3;
|
||||
|
||||
// get two of the triangle's edges
|
||||
const QVector4D v21 = v2-v1;
|
||||
const QVector4D v31 = v3-v1;
|
||||
|
||||
// check the angle between both
|
||||
const float angle = QVector2D::dotProduct(v21.toVector2D(), v31.toVector2D());
|
||||
return angle > 0;
|
||||
|
||||
// const QVector3D n = QVector3D::crossProduct(v21.toVector3D(), v31.toVector3D());
|
||||
// return n.z() > 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // MAP_HELPER_H
|
||||
126
ui/map/gl/GLLines.h
Normal file
126
ui/map/gl/GLLines.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#ifndef GLLINES_H
|
||||
#define GLLINES_H
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include "GL.h"
|
||||
#include "GLHelper.h"
|
||||
|
||||
#include <Indoor/geo/Point3.h>
|
||||
|
||||
class GLLines : protected QOpenGLFunctions {
|
||||
|
||||
private:
|
||||
|
||||
QOpenGLBuffer arrayBuf;
|
||||
QOpenGLBuffer indexBuf;
|
||||
|
||||
std::vector<Vert> vertices;
|
||||
std::vector<GLushort> indices;
|
||||
|
||||
int mode = GL_LINES;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
GLLines() : arrayBuf(QOpenGLBuffer::VertexBuffer), indexBuf(QOpenGLBuffer::IndexBuffer) {
|
||||
;
|
||||
}
|
||||
|
||||
/** dtor */
|
||||
~GLLines() {
|
||||
arrayBuf.destroy();
|
||||
indexBuf.destroy();
|
||||
}
|
||||
|
||||
/** add a new face to this element */
|
||||
void addLine(const QVector3D& p1, const QVector3D& p2) {
|
||||
|
||||
// add vertices (remove duplicates!)
|
||||
const int i1 = addOnce(p1);
|
||||
const int i2 = addOnce(p2);
|
||||
|
||||
// add indices
|
||||
indices.push_back(i1);
|
||||
indices.push_back(i2);
|
||||
|
||||
}
|
||||
|
||||
void addVertex(const QVector3D& p1) {
|
||||
const int i1 = addOnce(p1);
|
||||
indices.push_back(i1);
|
||||
}
|
||||
|
||||
|
||||
/** build the underlying buffers */
|
||||
void build() {
|
||||
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
// Transfer vertex data to VBO 0
|
||||
arrayBuf.create();
|
||||
arrayBuf.bind();
|
||||
arrayBuf.allocate(vertices.data(), vertices.size() * sizeof(vertices[0]));
|
||||
|
||||
// Transfer index data to VBO 1
|
||||
indexBuf.create();
|
||||
indexBuf.bind();
|
||||
indexBuf.allocate(indices.data(), indices.size() * sizeof(indices[0]));
|
||||
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
if (indexBuf.isCreated()) {indexBuf.destroy();}
|
||||
if (arrayBuf.isCreated()) {arrayBuf.destroy();}
|
||||
build();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
indices.clear();
|
||||
vertices.clear();
|
||||
}
|
||||
|
||||
void setMode(const int mode) {
|
||||
this->mode = mode;
|
||||
}
|
||||
|
||||
/** render the element */
|
||||
void render(QOpenGLShaderProgram *program) {
|
||||
|
||||
// Tell OpenGL which VBOs to use
|
||||
arrayBuf.bind();
|
||||
indexBuf.bind();
|
||||
|
||||
// vertices
|
||||
int vertLoc = program->attributeLocation("a_position");
|
||||
program->enableAttributeArray(vertLoc);
|
||||
program->setAttributeBuffer(vertLoc, GL_FLOAT, vertices[0].getVertOffset(), 3, sizeof(vertices[0]));
|
||||
|
||||
// Draw cube geometry using indices from VBO 1
|
||||
glDrawElements(mode, indices.size(), GL_UNSIGNED_SHORT, 0);
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/** to conserve memory, avoid duplicate VNTs! */
|
||||
int addOnce(const Vert& vnt) {
|
||||
|
||||
const auto it = std::find(vertices.begin(), vertices.end(), vnt);
|
||||
|
||||
if (it == vertices.end()) {
|
||||
const int idx = vertices.size();
|
||||
vertices.push_back(vnt);
|
||||
return idx;
|
||||
} else {
|
||||
const int idx = it - vertices.begin();
|
||||
return idx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // GLLINES_H
|
||||
111
ui/map/gl/GLPoints.h
Normal file
111
ui/map/gl/GLPoints.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#ifndef GLPOINTS_H
|
||||
#define GLPOINTS_H
|
||||
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include "GL.h"
|
||||
#include "GLHelper.h"
|
||||
|
||||
#include <Indoor/geo/Point3.h>
|
||||
|
||||
class GLPoints : protected QOpenGLFunctions {
|
||||
|
||||
private:
|
||||
|
||||
QOpenGLBuffer arrayBuf;
|
||||
QOpenGLBuffer indexBuf;
|
||||
|
||||
std::vector<VertColor> vertices;
|
||||
std::vector<GLuint> indices;
|
||||
|
||||
int mode = GL_POINTS;
|
||||
bool initOnce = true;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
GLPoints() : arrayBuf(QOpenGLBuffer::VertexBuffer), indexBuf(QOpenGLBuffer::IndexBuffer) {
|
||||
alloc();
|
||||
}
|
||||
|
||||
/** dtor */
|
||||
~GLPoints() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
/** add a new face to this element */
|
||||
void addPoint(const QVector3D& pt, const QColor& color) {
|
||||
indices.push_back(vertices.size());
|
||||
QVector3D c(color.redF(), color.greenF(), color.blueF());
|
||||
vertices.push_back(VertColor(pt, c));
|
||||
}
|
||||
|
||||
|
||||
void alloc() {
|
||||
if (!indexBuf.isCreated()) {indexBuf.create();}
|
||||
if (!arrayBuf.isCreated()) {arrayBuf.create();}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (indexBuf.isCreated()) {indexBuf.destroy();}
|
||||
if (arrayBuf.isCreated()) {arrayBuf.destroy();}
|
||||
}
|
||||
|
||||
/** build the underlying buffers */
|
||||
void build() {
|
||||
|
||||
// Transfer vertex data to VBO 0
|
||||
arrayBuf.bind();
|
||||
arrayBuf.allocate(vertices.data(), vertices.size() * sizeof(vertices[0]));
|
||||
|
||||
// Transfer index data to VBO 1
|
||||
indexBuf.bind();
|
||||
indexBuf.allocate(indices.data(), indices.size() * sizeof(indices[0]));
|
||||
|
||||
}
|
||||
|
||||
void initGL() {
|
||||
initializeOpenGLFunctions();
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
build();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
indices.clear();
|
||||
vertices.clear();
|
||||
}
|
||||
|
||||
void setMode(const int mode) {
|
||||
this->mode = mode;
|
||||
}
|
||||
|
||||
/** render the element */
|
||||
void render(QOpenGLShaderProgram *program) {
|
||||
|
||||
if (indices.empty()) {return;}
|
||||
|
||||
// Tell OpenGL which VBOs to use
|
||||
arrayBuf.bind();
|
||||
indexBuf.bind();
|
||||
|
||||
// vertices
|
||||
int vertLoc = program->attributeLocation("a_position");
|
||||
program->enableAttributeArray(vertLoc);
|
||||
program->setAttributeBuffer(vertLoc, GL_FLOAT, vertices[0].getVertOffset(), 3, sizeof(vertices[0]));
|
||||
|
||||
// colors
|
||||
int colorLoc = program->attributeLocation("a_color");
|
||||
program->enableAttributeArray(colorLoc);
|
||||
program->setAttributeBuffer(colorLoc, GL_FLOAT, vertices[0].getColorOffset(), 3, sizeof(vertices[0]));
|
||||
|
||||
// Draw cube geometry using indices from VBO 1
|
||||
glDrawElements(mode, indices.size(), GL_UNSIGNED_INT, 0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // GLPOINTS_H
|
||||
210
ui/map/gl/GLTriangles.h
Normal file
210
ui/map/gl/GLTriangles.h
Normal file
@@ -0,0 +1,210 @@
|
||||
#ifndef GLTRIANGLES_H
|
||||
#define GLTRIANGLES_H
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include "GL.h"
|
||||
#include "GLHelper.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <Indoor/geo/Point3.h>
|
||||
|
||||
template <typename T> class GLTriangles : protected QOpenGLFunctions {
|
||||
|
||||
private:
|
||||
|
||||
QOpenGLBuffer arrayBuf;
|
||||
QOpenGLBuffer indexBuf;
|
||||
QOpenGLTexture* textures[4];
|
||||
|
||||
std::vector<T> vertices;
|
||||
std::vector<GLushort> indices;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
GLTriangles() : arrayBuf(QOpenGLBuffer::VertexBuffer), indexBuf(QOpenGLBuffer::IndexBuffer), textures() {
|
||||
;
|
||||
}
|
||||
|
||||
/** dtor */
|
||||
~GLTriangles() {
|
||||
|
||||
arrayBuf.destroy();
|
||||
indexBuf.destroy();
|
||||
|
||||
for (int i = 0; i < 4; ++i) {delete textures[i];}
|
||||
|
||||
}
|
||||
|
||||
/** set the to-be-used texture */
|
||||
void setTexture(const int slot, const QString& textureFile) {
|
||||
|
||||
const QImage img(textureFile);
|
||||
if (img.width() <= 0) {throw "error";}
|
||||
|
||||
QOpenGLTexture* texture = new QOpenGLTexture(img);
|
||||
texture->setMinificationFilter(QOpenGLTexture::Linear);
|
||||
texture->setMagnificationFilter(QOpenGLTexture::Linear);
|
||||
texture->setWrapMode(QOpenGLTexture::Repeat);
|
||||
texture->generateMipMaps();
|
||||
textures[slot] = texture;
|
||||
|
||||
}
|
||||
|
||||
void setDiffuse(const QString& textureFile) {
|
||||
setTexture(0, textureFile);
|
||||
}
|
||||
|
||||
void setNormalMap(const QString& textureFile) {
|
||||
setTexture(1, textureFile);
|
||||
}
|
||||
|
||||
/** add a new face to this element */
|
||||
void addFace(const T& vnt1, const T& vnt2, const T& vnt3) {
|
||||
addFace(vnt1, vnt2, vnt3, 0);
|
||||
}
|
||||
|
||||
/** add a new face to this element */
|
||||
void addFaceCCW(const T& vnt1, const T& vnt2, const T& vnt3) {
|
||||
addFace(vnt1, vnt2, vnt3, 1);
|
||||
}
|
||||
|
||||
/** add a new face to this element */
|
||||
void addFaceCW(const T& vnt1, const T& vnt2, const T& vnt3) {
|
||||
addFace(vnt1, vnt2, vnt3, 2);
|
||||
}
|
||||
|
||||
/** add a new quad to this element */
|
||||
void addQuadCCW(const T& vnt1, const T& vnt2, const T& vnt3, const T& vnt4) {
|
||||
addFace(vnt1, vnt2, vnt3, 1);
|
||||
addFace(vnt3, vnt4, vnt1, 1);
|
||||
}
|
||||
|
||||
/** add a new quad to this element */
|
||||
void addQuadCW(const T& vnt1, const T& vnt2, const T& vnt3, const T& vnt4) {
|
||||
addFace(vnt1, vnt2, vnt3, 2);
|
||||
addFace(vnt3, vnt4, vnt1, 2);
|
||||
}
|
||||
|
||||
/** add a new quad to this element */
|
||||
void addQuad(const T& vnt1, const T& vnt2, const T& vnt3, const T& vnt4) {
|
||||
addFace(vnt1, vnt2, vnt3, 0);
|
||||
addFace(vnt3, vnt4, vnt1, 0);
|
||||
}
|
||||
|
||||
/** build the underlying buffers */
|
||||
void build() {
|
||||
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
// Transfer vertex data to VBO 0
|
||||
arrayBuf.create();
|
||||
arrayBuf.bind();
|
||||
arrayBuf.allocate(vertices.data(), vertices.size() * sizeof(vertices[0]));
|
||||
|
||||
// Transfer index data to VBO 1
|
||||
indexBuf.create();
|
||||
indexBuf.bind();
|
||||
indexBuf.allocate(indices.data(), indices.size() * sizeof(indices[0]));
|
||||
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
if (indexBuf.isCreated()) {indexBuf.destroy();}
|
||||
if (arrayBuf.isCreated()) {arrayBuf.destroy();}
|
||||
build();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
indices.clear();
|
||||
vertices.clear();
|
||||
}
|
||||
|
||||
/** render the element */
|
||||
void render(QOpenGLShaderProgram* program) {
|
||||
|
||||
// Tell OpenGL which VBOs to use
|
||||
arrayBuf.bind();
|
||||
indexBuf.bind();
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (textures[i]) { textures[i]->bind(i); }
|
||||
}
|
||||
|
||||
// vertices
|
||||
int vertLoc = program->attributeLocation("a_position");
|
||||
program->enableAttributeArray(vertLoc);
|
||||
program->setAttributeBuffer(vertLoc, GL_FLOAT, vertices[0].getVertOffset(), 3, sizeof(vertices[0]));
|
||||
|
||||
// Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
|
||||
int normLoc = program->attributeLocation("a_normal");
|
||||
program->enableAttributeArray(normLoc);
|
||||
program->setAttributeBuffer(normLoc, GL_FLOAT, vertices[0].getNormOffset(), 3, sizeof(vertices[0]));
|
||||
|
||||
int texcoordLocation = program->attributeLocation("a_texcoord");
|
||||
program->enableAttributeArray(texcoordLocation);
|
||||
program->setAttributeBuffer(texcoordLocation, GL_FLOAT, vertices[0].getTexOffset(), 2, sizeof(vertices[0]));
|
||||
|
||||
// bind tangent data?
|
||||
if (T::hasTangent()) {
|
||||
int tanLocation = program->attributeLocation("a_tangent");
|
||||
program->enableAttributeArray(tanLocation);
|
||||
program->setAttributeBuffer(tanLocation, GL_FLOAT, vertices[0].getTanOffset(), 3, sizeof(vertices[0]));
|
||||
}
|
||||
|
||||
|
||||
// texture
|
||||
program->setUniformValue("texture", 0);
|
||||
|
||||
// Draw cube geometry using indices from VBO 1
|
||||
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, 0);
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void addFace(const T& vnt1, const T& vnt2, const T& vnt3, const int mode) {
|
||||
|
||||
// add vertices (remove duplicates!)
|
||||
const int i1 = addOnce(vnt1);
|
||||
const int i2 = addOnce(vnt2);
|
||||
const int i3 = addOnce(vnt3);
|
||||
|
||||
// get current orientation
|
||||
const bool ccw = GLHelper::isCCW(vnt1.vert, vnt2.vert, vnt3.vert);
|
||||
|
||||
// create indices
|
||||
if (mode == 0 || (mode == 1 && ccw) || (mode == 2 && !ccw) ) {
|
||||
indices.push_back(i1);
|
||||
indices.push_back(i2);
|
||||
indices.push_back(i3);
|
||||
} else {
|
||||
indices.push_back(i3);
|
||||
indices.push_back(i2);
|
||||
indices.push_back(i1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** to conserve memory, avoid duplicate VNTs! */
|
||||
int addOnce(const T& vnt) {
|
||||
|
||||
const auto it = std::find(vertices.begin(), vertices.end(), vnt);
|
||||
|
||||
if (it == vertices.end()) {
|
||||
const int idx = vertices.size();
|
||||
vertices.push_back(vnt);
|
||||
return idx;
|
||||
} else {
|
||||
const int idx = it - vertices.begin();
|
||||
return idx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // GLTRIANGLES_H
|
||||
31
ui/map/gl/Shader.h
Normal file
31
ui/map/gl/Shader.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef SHADER_H
|
||||
#define SHADER_H
|
||||
|
||||
#include <QOpenGLShaderProgram>
|
||||
|
||||
/**
|
||||
* just some helper methods
|
||||
*/
|
||||
class Shader {
|
||||
|
||||
private:
|
||||
|
||||
QOpenGLShaderProgram program;
|
||||
|
||||
public:
|
||||
|
||||
/** get the underlying program */
|
||||
QOpenGLShaderProgram* getProgram() {return &program;}
|
||||
|
||||
/** helper method to build the shader */
|
||||
void loadShaderFromFile(const QString& vertex, const QString& fragment) {
|
||||
if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, vertex)) {throw "1";}
|
||||
if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, fragment)) {throw "2";}
|
||||
if (!program.link()) {throw "3";}
|
||||
if (!program.bind()) {throw "4";}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // SHADER_H
|
||||
55
ui/menu/MainMenu.cpp
Normal file
55
ui/menu/MainMenu.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
#include "MainMenu.h"
|
||||
#include "../Icons.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QGridLayout>
|
||||
|
||||
#include <Indoor/Assertions.h>
|
||||
|
||||
MainMenu::MainMenu(QWidget* parent) : QWidget(parent) {
|
||||
|
||||
setMinimumHeight(64);
|
||||
|
||||
QGridLayout* lay = new QGridLayout(this);
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
|
||||
btnLoadMap = getButton("load");
|
||||
Assert::isTrue(connect(btnLoadMap, &QPushButton::clicked, this, &MainMenu::onLoadButton), "connect() failed");
|
||||
lay->addWidget(btnLoadMap, row, col, 1,1,Qt::AlignTop); ++col;
|
||||
|
||||
btnDebug = getButton("bug");
|
||||
Assert::isTrue(connect(btnDebug, &QPushButton::clicked, this, &MainMenu::onDebugButton), "connect() failed");
|
||||
lay->addWidget(btnDebug, row, col, 1,1,Qt::AlignTop); ++col;
|
||||
|
||||
btnCamera = getButton("camera");
|
||||
Assert::isTrue(connect(btnCamera, &QPushButton::clicked, this, &MainMenu::onCameraButton), "connect() failed");
|
||||
lay->addWidget(btnCamera, row, col, 1,1,Qt::AlignTop); ++col;
|
||||
|
||||
btnTransparent = getButton("cube");
|
||||
Assert::isTrue(connect(btnTransparent, &QPushButton::clicked, this, &MainMenu::onTransparentButton), "connect() failed");
|
||||
lay->addWidget(btnTransparent, row, col, 1,1,Qt::AlignTop); ++col;
|
||||
|
||||
btnStart = getButton("run");
|
||||
Assert::isTrue(connect(btnStart, &QPushButton::clicked, this, &MainMenu::onStartButton), "connect() failed");
|
||||
lay->addWidget(btnStart, row, col, 1,1,Qt::AlignTop); ++col;
|
||||
|
||||
|
||||
}
|
||||
|
||||
QPushButton* MainMenu::getButton(const std::string& icon) {
|
||||
|
||||
const int size = 48;
|
||||
const int border = 4;
|
||||
|
||||
QPushButton* btn = new QPushButton(Icons::getIcon(icon, size), "");
|
||||
btn->setIconSize(QSize(size,size));
|
||||
btn->setMinimumHeight(size+border);
|
||||
btn->setMaximumHeight(size+border);
|
||||
btn->setMinimumWidth(size+border);
|
||||
btn->setMaximumWidth(size+border);
|
||||
|
||||
return btn;
|
||||
|
||||
}
|
||||
36
ui/menu/MainMenu.h
Normal file
36
ui/menu/MainMenu.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef MAINMENU_H
|
||||
#define MAINMENU_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QPushButton;
|
||||
|
||||
class MainMenu : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
explicit MainMenu(QWidget* parent);
|
||||
|
||||
signals:
|
||||
|
||||
void onLoadButton();
|
||||
void onStartButton();
|
||||
void onDebugButton();
|
||||
void onCameraButton();
|
||||
void onTransparentButton();
|
||||
|
||||
private:
|
||||
|
||||
QPushButton* getButton(const std::string& icon);
|
||||
|
||||
QPushButton* btnLoadMap;
|
||||
QPushButton* btnStart;
|
||||
QPushButton* btnDebug;
|
||||
QPushButton* btnCamera;
|
||||
QPushButton* btnTransparent;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user