diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/boid.cc | 108 | ||||
-rw-r--r-- | src/drone_controller.cc | 102 | ||||
-rw-r--r-- | src/drone_controller.hh | 57 | ||||
-rw-r--r-- | src/main.cc | 11 | ||||
-rw-r--r-- | src/main_window.cc | 73 | ||||
-rw-r--r-- | src/main_window.hh | 36 | ||||
-rw-r--r-- | src/opengl_mesh.cc | 19 | ||||
-rw-r--r-- | src/opengl_mesh.hh | 18 | ||||
-rw-r--r-- | src/opengl_widget.cc | 143 | ||||
-rw-r--r-- | src/opengl_widget.hh | 35 |
10 files changed, 602 insertions, 0 deletions
diff --git a/src/boid.cc b/src/boid.cc new file mode 100644 index 0000000..9eeea41 --- /dev/null +++ b/src/boid.cc @@ -0,0 +1,108 @@ +#include "main_window.hh" + +#include <QVector3D> +#include <QRandomGenerator> + + +struct Fish { + QVector3D pos; + QVector3D vel; + float size; + float dist = 10; + float sqdist; + + OpenGLMesh mesh; + + Fish(QVector3D pos, OpenGLMesh mesh) + :pos(pos), + sqdist(dist * dist), + mesh(mesh) {} + + void step() { + pos += vel; + mesh.mat.setToIdentity(); + QMatrix4x4 rot; + rot.lookAt(pos, vel, {0, 1, 0}); + mesh.mat.translate(pos); + // mesh.mat = rot * mesh.mat; + } + + bool in_neighborhood(const QVector3D &p) const { + return (p - pos).lengthSquared() < sqdist; + } +}; + + +struct Banc { + QVector<Fish> fishes; + + Banc() {} + + Banc(size_t nfishes) { + OpenGLMesh fish_mesh = OpenGLMesh({ + 0, .1, 0, + -.0866, -.05, 0, + .0866, -.05, 0, + }); + QRandomGenerator *rng = QRandomGenerator::global(); + for (size_t i = 0; i < nfishes; i++) { + Fish fish({ + (float) (rng->generateDouble() * 3 - 1.5), + (float) (rng->generateDouble() * 3 - 1.5), + (float) (rng->generateDouble() * 3 - 1.5) + }, fish_mesh); + fishes.append(fish); + } + } + + void step() { + QVector<Fish> neighbors; + for (Fish &fish : fishes) { + QVector3D neighbors_cog; + QVector3D neighbors_vel; + size_t nneighbors = 0; + for (Fish &fish_b : fishes) { + if (fish_b.in_neighborhood(fish.pos)) { + nneighbors++; + neighbors_cog += fish_b.pos; + neighbors_vel += fish_b.vel; + } + } + neighbors_cog /= nneighbors; + neighbors_vel /= nneighbors; + QVector3D v1 = -(fish.pos - neighbors_cog); + QVector3D v2 = fish.vel - neighbors_vel; + QVector3D v3 = neighbors_cog - fish.pos; + QVector3D v = 1.5 * v1 + v2 + v3; + float L = .001; + fish.vel = (1-L) * fish.vel + L * v; + fish.step(); + } + } +}; + + +Banc banc; + +void MainWindow::init() { + banc = Banc(10); + for (Fish &fish : banc.fishes) { + glw.meshes.append(&fish.mesh); + } +} + + +void MainWindow::step() { + banc.step(); + OpenGLWidget::instance->update(); +} + + +void OpenGLWidget::paintGL() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + for (const Fish &fish : banc.fishes) { + glUniformMatrix4fv(model_attr, 1, GL_FALSE, fish.mesh.mat.data()); + glBindVertexArray(fish.mesh.vao); + glDrawArrays(GL_TRIANGLES, 0, fish.mesh.nverts); + } +} diff --git a/src/drone_controller.cc b/src/drone_controller.cc new file mode 100644 index 0000000..46acc7e --- /dev/null +++ b/src/drone_controller.cc @@ -0,0 +1,102 @@ +#include "drone_controller.hh" + +#include <QJsonArray> +#include <QDebug> + + +Waypoint::Waypoint(unsigned frame, QVector3D pos) + :frame(frame), + pos(pos) {} + + +Waypoint::Waypoint(const QJsonObject &json) + :Waypoint(json["frame"].toInt(), + QVector3D(json["position"]["lng_X"].toInt(), + json["position"]["alt_Y"].toInt(), + json["position"]["lat_Z"].toInt())) {} + + +Drone::Drone(const QJsonObject &json) { + QJsonArray ja = json["waypoints"].toArray(); + waypoints.reserve(ja.size()); + for (const QJsonValue &o : ja) { + waypoints.append(Waypoint(o.toObject())); + } +} + + +const QVector<Waypoint> Drone::getWaypoints() const { + return waypoints; +} + + +DroneController::DroneController(const QJsonObject &json) + :framerate(json["framerate"].toInt()) { + + // TODO: REMOVE!! + framerate = 1; + + QJsonArray ja = json["drones"].toArray(); + drones.reserve(ja.size()); + for (const QJsonValue &o : ja) { + drones.append(Drone(o.toObject())); + } + for (const Drone &d : drones) { + for (const Waypoint &wp : d.getWaypoints()) { + if (wp.frame > duration) duration = wp.frame; + } + } + + connect(&timer, &QTimer::timeout, this, &DroneController::step); + pause(); +} + + +DroneController::~DroneController() { +} + + +int DroneController::getDuration() const { + return duration; +} + + +void DroneController::step() { + qDebug() << "frame " << frame << "/" << duration + << " (" << (double) frame / duration * 100 << "%)"; + emit frameChanged(frame); + frame++; +} + + +void DroneController::play() { + paused = false; + timer.start(1000. / framerate); + qDebug() << "playing"; +} + + +void DroneController::pause() { + paused = true; + timer.stop(); + qDebug() << "pausing"; +} + + +void DroneController::suspend() { + bool old_paused = paused; + pause(); + paused = old_paused; +} + + +void DroneController::resume() { + if (!paused) play(); +} + + +void DroneController::seek(int frame) { + if (this->frame == frame) return; + this->frame = frame; + step(); +} diff --git a/src/drone_controller.hh b/src/drone_controller.hh new file mode 100644 index 0000000..484cd57 --- /dev/null +++ b/src/drone_controller.hh @@ -0,0 +1,57 @@ +#ifndef DRONE_CONTROLLER_HH +#define DRONE_CONTROLLER_HH + +#include <QJsonObject> +#include <QVector3D> +#include <QTimer> + + +struct Waypoint { + int frame; + QVector3D pos; + + Waypoint(unsigned frame, QVector3D pos); + Waypoint(const QJsonObject &json); +}; + + +class Drone { + QVector<Waypoint> waypoints; + +public: + Drone(const QJsonObject &json); + const QVector<Waypoint> getWaypoints() const; +}; + + +class DroneController : public QObject { + Q_OBJECT + + int framerate; + int frame = 0; + int duration = 0; + QVector<Drone> drones; + QTimer timer; + bool paused = true; + +public: + DroneController(const QJsonObject &json); + ~DroneController(); + int getDuration() const; + +signals: + void frameChanged(int frame); + +private slots: + void step(); + +public slots: + void play(); + void pause(); + void suspend(); + void resume(); + void seek(int frame); +}; + + +#endif diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..c70cfaa --- /dev/null +++ b/src/main.cc @@ -0,0 +1,11 @@ +#include "main_window.hh" + +#include <QApplication> + + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + MainWindow mw; + mw.show(); + return app.exec(); +} diff --git a/src/main_window.cc b/src/main_window.cc new file mode 100644 index 0000000..5e58660 --- /dev/null +++ b/src/main_window.cc @@ -0,0 +1,73 @@ +#include "main_window.hh" + +#include <QApplication> +#include <QFile> +#include <QFileDialog> +#include <QJsonDocument> +#include <QStyle> + + +MainWindow::MainWindow(QWidget *parent) { + (void) parent; + connect(&glw, &OpenGLWidget::initialized, + this, &MainWindow::onOpenGLWidgetInitialized); + // connect(&timer, &QTimer::timeout, this, &MainWindow::step); + setCentralWidget(&glw); + addToolBar(Qt::TopToolBarArea, &top_tb); + open_action = top_tb.addAction("Ouvrir…", [&]() { + open(QFileDialog::getOpenFileName(this, "Ouvrir un fichier JSON")); + }); + open_action->setEnabled(false); + addToolBar(Qt::BottomToolBarArea, &bottom_tb); + playpause_action = bottom_tb.addAction(style()->standardIcon(QStyle::SP_MediaPlay), "", + this, &MainWindow::play); + playpause_action->setEnabled(false); + slider = new QSlider(Qt::Horizontal); + bottom_tb.addWidget(slider); + slider->setEnabled(false); +} + + +void MainWindow::play() { + dc->play(); + playpause_action->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); + disconnect(playpause_action, &QAction::triggered, nullptr, nullptr); + connect(playpause_action, &QAction::triggered, this, &MainWindow::pause); +} + + +void MainWindow::pause() { + dc->pause(); + playpause_action->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + disconnect(playpause_action, &QAction::triggered, nullptr, nullptr); + connect(playpause_action, &QAction::triggered, this, &MainWindow::play); +} + + +void MainWindow::open(const QString &path) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qWarning("Impossible d’ouvrir le fichier."); + return; + } + QByteArray data = file.readAll(); + QJsonDocument json_doc = QJsonDocument::fromJson(data); + if (dc) delete dc; + dc = new DroneController(json_doc.object()); + playpause_action->setEnabled(true); + slider->setMinimum(0); + slider->setMaximum(dc->getDuration()); + connect(slider, &QSlider::sliderPressed, dc, &DroneController::suspend); + connect(slider, &QSlider::sliderReleased, dc, &DroneController::resume); + connect(slider, &QSlider::valueChanged, dc, &DroneController::seek); + connect(dc, &DroneController::frameChanged, slider, &QSlider::setValue); + slider->setEnabled(true); +} + + +void MainWindow::onOpenGLWidgetInitialized() { + if (qApp->arguments().size() == 2) { + open(qApp->arguments().at(1)); + } + open_action->setEnabled(true); +} diff --git a/src/main_window.hh b/src/main_window.hh new file mode 100644 index 0000000..1e8868a --- /dev/null +++ b/src/main_window.hh @@ -0,0 +1,36 @@ +#ifndef MAIN_WINDOW_HH +#define MAIN_WINDOW_HH + +#include "opengl_widget.hh" +#include "drone_controller.hh" + +#include <QMainWindow> +#include <QTimer> +#include <QToolBar> +#include <QSlider> + + +class MainWindow : public QMainWindow { + Q_OBJECT + + OpenGLWidget glw; + QTimer timer; + QToolBar top_tb; + QToolBar bottom_tb; + QAction *open_action; + QAction *playpause_action; + QSlider *slider; + DroneController *dc = nullptr; + void play(); + void pause(); + +public: + MainWindow(QWidget *parent=nullptr); + void open(const QString &path); + +private slots: + void onOpenGLWidgetInitialized(); +}; + + +#endif diff --git a/src/opengl_mesh.cc b/src/opengl_mesh.cc new file mode 100644 index 0000000..cca91da --- /dev/null +++ b/src/opengl_mesh.cc @@ -0,0 +1,19 @@ +#include "opengl_mesh.hh" + +#include "opengl_widget.hh" + + +OpenGLMesh::OpenGLMesh(QVector<float> verts) { + OpenGLWidget::instance->makeCurrent(); + QOpenGLFunctions_4_4_Core *glf = OpenGLWidget::instance; + nverts = verts.size() / 3; + glf->glGenVertexArrays(1, &vao); + glf->glGenBuffers(1, &vbo); + glf->glBindVertexArray(vao); + glf->glBindBuffer(GL_ARRAY_BUFFER, vbo); + glf->glBufferData(GL_ARRAY_BUFFER, nverts * 3 * sizeof (GLfloat), verts.data(), GL_STATIC_DRAW); + glf->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); + glf->glEnableVertexAttribArray(0); + glf->glBindVertexArray(0); + OpenGLWidget::instance->doneCurrent(); +} diff --git a/src/opengl_mesh.hh b/src/opengl_mesh.hh new file mode 100644 index 0000000..e3b8ad2 --- /dev/null +++ b/src/opengl_mesh.hh @@ -0,0 +1,18 @@ +#ifndef MESH_HH +#define MESH_HH + +#include <QMatrix4x4> +#include <QVector> +#include <QOpenGLFunctions> + + +struct OpenGLMesh { + GLuint vao, vbo; + unsigned nverts; + QMatrix4x4 mat; + + OpenGLMesh(QVector<float> verts); +}; + + +#endif diff --git a/src/opengl_widget.cc b/src/opengl_widget.cc new file mode 100644 index 0000000..0251491 --- /dev/null +++ b/src/opengl_widget.cc @@ -0,0 +1,143 @@ +#include "opengl_widget.hh" + + +static const GLchar *vertex_shader_source = R"glsl( +#version 330 core + +layout(location = 0) in vec3 pos; + +uniform mat4 proj; +uniform mat4 view; +uniform mat4 model; + +void main() { + gl_Position = proj * view * model * vec4(pos, 1.0); +} +)glsl"; + +static const GLchar *fragment_shader_source = R"glsl( +#version 330 core + +out vec4 final_col; + +void main() { + final_col = vec4(0, 0, 0, 1); +} +)glsl"; + + +static void GLAPIENTRY opengl_debug_cb(GLenum source, GLenum type, GLuint id, + GLenum severity, GLsizei length, + const GLchar* message, + const void* userParam) { + (void) source; (void) type; (void) id; (void) severity; (void) length; + (void) userParam; + qDebug() << "OpenGL debug output:" << message; +} + + +OpenGLWidget *OpenGLWidget::instance = nullptr; + + +OpenGLWidget::OpenGLWidget(QWidget *parent) + :QOpenGLWidget(parent) { + OpenGLWidget::instance = this; +} + + +OpenGLWidget::~OpenGLWidget() { + OpenGLWidget::instance = nullptr; +} + + +void OpenGLWidget::initializeGL() { + initializeOpenGLFunctions(); + GLint major, minor; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + qDebug("OpenGL version %d.%d", major, minor); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(opengl_debug_cb, 0); + + /* Compile the vertex shader. */ + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL); + glCompileShader(vertex_shader); + GLint status; + glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + char log[1024]; + glGetShaderInfoLog(vertex_shader, sizeof log, NULL, log); + fprintf(stderr, "Failed to compile the vertex shader: %s\n", log); + exit(1); + } + + /* Compile the fragment shader. */ + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL); + glCompileShader(fragment_shader); + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + char log[1024]; + glGetShaderInfoLog(fragment_shader, sizeof log, NULL, log); + fprintf(stderr, "Failed to compile the fragment shader: %s\n", log); + exit(1); + } + + /* Link the shader program. */ + GLuint shader_program = glCreateProgram(); + glAttachShader(shader_program, vertex_shader); + glAttachShader(shader_program, fragment_shader); + glBindFragDataLocation(shader_program, 0, "out_color"); + glLinkProgram(shader_program); + glGetProgramiv(shader_program, GL_LINK_STATUS, &status); + if (status != GL_TRUE) { + char log[1024]; + glGetProgramInfoLog(shader_program, sizeof log, NULL, log); + fprintf(stderr, "Failed to link the shader program: %s\n", log); + exit(1); + } + + /* Use it. */ + glUseProgram(shader_program); + + /* Get the position attribute. */ + pos_attr = glGetAttribLocation(shader_program, "pos"); + + proj_attr = glGetUniformLocation(shader_program, "proj"); + view_attr = glGetUniformLocation(shader_program, "view"); + model_attr = glGetUniformLocation(shader_program, "model"); + + QMatrix4x4 view; + view.translate(0, 0, 5); + glUniformMatrix4fv(view_attr, 1, GL_FALSE, view.data()); + + QMatrix4x4 trans; + trans.translate(0, 0, -5); + glUniformMatrix4fv(view_attr, 1, GL_FALSE, trans.data()); + + glClearColor(1, 1, 1, 0); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_MULTISAMPLE); + + emit initialized(); +} + + +void OpenGLWidget::resizeGL(int w, int h) { + QMatrix4x4 projection; + projection.perspective(FOV, (float) w/h, .1, 100); + glUniformMatrix4fv(proj_attr, 1, GL_FALSE, projection.data()); +} + + +void OpenGLWidget::paintGL() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + for (const OpenGLMesh &mesh : meshes) { + glUniformMatrix4fv(model_attr, 1, GL_FALSE, mesh.mat.data()); + glBindVertexArray(mesh.vao); + glDrawArrays(GL_TRIANGLES, 0, mesh.nverts); + } +} diff --git a/src/opengl_widget.hh b/src/opengl_widget.hh new file mode 100644 index 0000000..48cb276 --- /dev/null +++ b/src/opengl_widget.hh @@ -0,0 +1,35 @@ +#ifndef OPENGL_WIDGET_HH +#define OPENGL_WIDGET_HH + +#include "opengl_mesh.hh" + +#include <QOpenGLWidget> +#include <QMatrix4x4> +#include <QOpenGLFunctions_4_4_Core> +#include <QOpenGLShaderProgram> + +#define FOV 70 + + +class OpenGLWidget : public QOpenGLWidget, public QOpenGLFunctions_4_4_Core { + Q_OBJECT + + GLuint pos_attr, proj_attr, view_attr, model_attr; + +public: + static OpenGLWidget *instance; + + QVector<OpenGLMesh> meshes; + + OpenGLWidget(QWidget *parent=nullptr); + ~OpenGLWidget(); + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + +signals: + void initialized(); +}; + + +#endif |