Skip to content

File layer_renderer.hpp

File List > gui > layer_renderer.hpp

Go to the documentation of this file

#pragma once

#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <future>

#include "gui/camera.hpp"
#include "gui/layer.hpp"

class LayerRenderer : public QObject {
  Q_OBJECT

 protected:
  bool m_data_update_required = true;

 signals:
  void repaint_required() const;

 public:
  virtual void render(const Camera& camera) = 0;
  virtual ~LayerRenderer() = default;
  void data_update_required() {
    m_data_update_required = true;
    emit repaint_required();
  }

  static std::unique_ptr<LayerRenderer> create(std::shared_ptr<Layer> layer,
                                               const Coordinate3D<double>& offset);
};

static const char* vertexShaderSource =
    R"(
        #version 330 core
        in vec3 position;
        uniform float point_radius;
        uniform mat4 proj_matrix;
        out vec4 color;
        void main() {
            color = vec4(1.0, 1.0, 1.0, 0.5);
            vec4 pos = proj_matrix * vec4(position, 1.0);
            gl_Position = pos;
            float out_point_radius = point_radius / pos.w;
            if (out_point_radius < 1.0) {
                out_point_radius = 1.0;
            }
            gl_PointSize = out_point_radius;
        }
      )";

static const char* fragmentShaderSource =
    R"(
        #version 330 core
        in vec4 color;
        out vec4 fragColor;
        void main() {
            if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.5) {
                discard;
            }
            vec4 new_color = color * (gl_PointCoord.x + 1 - gl_PointCoord.y) * 0.6;
            fragColor = new_color;
        }
      )";

#define CHECK_SHADER_BIND(shader)                                                          \
  if (!shader->bind()) {                                                                   \
    std::cout << "Error: unable to bind a shader program: " << shader->log().toStdString() \
              << std::endl;                                                                \
    return;                                                                                \
  }

class LASLayerRenderer : public LayerRenderer {
  std::weak_ptr<LASLayer> m_layer;

  std::mutex m_data_mutex;
  bool m_data_updated;
  std::vector<GLfloat> m_points;

  QOpenGLVertexArrayObject m_vao;
  QOpenGLBuffer m_vbo;
  std::unique_ptr<QOpenGLShaderProgram> m_shader;

  int m_proj_matrix_loc = 0;
  int m_point_radius_loc = 0;

 public:
  LASLayerRenderer(std::shared_ptr<LASLayer> layer, const Coordinate3D<double>& offset)
      : m_layer(layer), m_data_updated(false) {
    std::future<void> future =
        std::async(std::launch::async, [this, layer, offset]() { load_data(layer, offset); });
  }

  void load_data(std::shared_ptr<LASLayer> layer, const Coordinate3D<double>& offset) {
    std::unique_lock<std::mutex> lock(layer->las_file().mutex(), std::try_to_lock);
    if (!lock.owns_lock()) {
      m_data_update_required = true;
      return;
    }
    m_data_update_required = false;
    m_points.reserve(layer->las_file().n_points() * 3);
    for (size_t i = 0; i < layer->las_file().n_points(); ++i) {
      auto point = layer->las_file()[i];
      m_points.push_back(point.x() - offset.x());
      m_points.push_back(point.y() - offset.y());
      m_points.push_back(point.z() - offset.z());
    }
    m_data_updated = true;
    emit repaint_required();
  }

  virtual void render(const Camera& camera) override {
    if (m_data_update_required) {
      Coordinate3D<double> world_offset = camera.world_offset();
      std::future<void> future = std::async(std::launch::async, [this, world_offset]() {
        if (auto layer = m_layer.lock()) {
          load_data(layer, world_offset);
        }
      });
      return;
    }
    if (!m_shader && m_data_updated) {
      std::unique_lock<std::mutex> lock(m_data_mutex, std::try_to_lock);
      if (!lock.owns_lock()) {
        return;
      }
      m_shader = std::make_unique<QOpenGLShaderProgram>();
      if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) {
        std::cout << "Error: unable to add a vertex shader: " << m_shader->log().toStdString()
                  << std::endl;
        return;
      }
      if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) {
        std::cout << "Error: unable to add a fragment shader: " << m_shader->log().toStdString()
                  << std::endl;
        return;
      }
      m_shader->bindAttributeLocation("position", 0);
      if (!m_shader->link()) {
        std::cout << "Error: unable to link a shader program: " << m_shader->log().toStdString()
                  << std::endl;
        return;
      }
      CHECK_SHADER_BIND(m_shader);

      m_vao.create();
      {
        QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);

        m_vbo.create();
        m_vbo.bind();

        m_data_updated = false;
        m_vbo.allocate(m_points.data(), m_points.size() * sizeof(GLfloat));

        m_shader->enableAttributeArray(0);
        m_shader->setAttributeBuffer(0, GL_FLOAT, 0, 3);

        m_proj_matrix_loc = m_shader->uniformLocation("proj_matrix");
        m_point_radius_loc = m_shader->uniformLocation("point_radius");

        m_vbo.release();
      }
      m_shader->release();
    }

    CHECK_SHADER_BIND(m_shader);
    QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();

    QMatrix4x4 camera_proj = camera.proj_matrix();

    CHECK_SHADER_BIND(m_shader);
    m_shader->setUniformValue(m_proj_matrix_loc, camera_proj);
    float point_radius = 0.1f * camera.projection_scale();
    m_shader->setUniformValue(m_point_radius_loc, point_radius);

    {
      QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);

      f->glDrawArrays(GL_POINTS, 0, m_points.size() / 3);
    }

    m_shader->release();
  }

  virtual ~LASLayerRenderer() = default;
};

inline std::unique_ptr<LayerRenderer> LayerRenderer::create(std::shared_ptr<Layer> layer,
                                                            const Coordinate3D<double>& offset) {
  if (auto las_layer = std::dynamic_pointer_cast<LASLayer>(layer)) {
    return std::make_unique<LASLayerRenderer>(las_layer, offset);
  }
  return nullptr;
}