From fb23c8d47f6dcef429423256d8dddcc0f7184fc4 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Sun, 4 Jun 2023 13:02:14 +0200 Subject: Further advances in nuklear port. Rendering now looks similar to Qt version, needs a few tweaks: * Proper multisampling * Background Missing features: * Barcharts * Interactivity (e.g. brushing/linking in all objects) * History view of interactions --- scatterplot.cpp | 303 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 286 insertions(+), 17 deletions(-) (limited to 'scatterplot.cpp') diff --git a/scatterplot.cpp b/scatterplot.cpp index fc31daa..a7194e8 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -3,8 +3,8 @@ #include #include +#include "colormap.h" #include "continuouscolorscale.h" -#include "geometry.h" // Glyphs settings static const Color DEFAULT_GLYPH_COLOR(255, 255, 255); @@ -31,28 +31,197 @@ static const Color SELECTION_COLOR(128, 128, 128, 96); // static const Qt::MouseButton NORMAL_BUTTON = Qt::LeftButton; // static const Qt::MouseButton SPECIAL_BUTTON = Qt::RightButton; +static const char *shaderVertex = R"EOF(#version 330 + +precision mediump float; + +uniform float colormask; +uniform mat4 transform; +uniform sampler2D colormap; + +layout (location = 0) in vec2 pos; +layout (location = 1) in float value; +// layout (location = 2) in float opacity; + +out VS_OUT { + vec4 color; +} vs_out; + +vec3 getRGB(float value) { + return texture(colormap, vec2(mix(0.005, 0.995, value), 0)).rgb; +} + +void main() +{ + vs_out.color = vec4(mix(vec3(1.0), getRGB(value), colormask), 1.0); + // vs_out.color = vec4(1.0); + gl_Position = transform * vec4(pos.xy, 0.0, 1.0); +} +)EOF"; + +static const char *shaderFragment = R"EOF(#version 330 +in vec4 color; + +out vec4 FragColor; + +void main() +{ + FragColor = color; + // FragColor = vec4(1.0); +} +)EOF"; + +static const char *shaderGeometry = R"EOF(#version 330 +layout (points) in; +layout (triangle_strip, max_vertices = 16) out; + +uniform float size; + +in VS_OUT { + vec4 color; +} gs_in[]; + +out vec4 color; + +#define PI 3.1415926538 + +void main() { + int num = 8; + float theta = 2 * PI / num; + float c = cos(theta); + float s = sin(theta); + vec4 p = vec4(size, 0.0, 0.0, 0.0); + // gs_in[0] as there is only one input vertex + color = gs_in[0].color; + for (int i = 0; i < num; i++) { + gl_Position = gl_in[0].gl_Position + p; + EmitVertex(); + + gl_Position = gl_in[0].gl_Position + vec4(p.x, -p.y, 0.0, 0.0); + EmitVertex(); + + p.xy = vec2(c*p.x - s*p.y, s*p.x + c*p.y); + } + EndPrimitive(); +} +)EOF"; + +static const char *shaderOutlineFragment = R"EOF(#version 330 +in vec4 color; + +out vec4 FragColor; + +void main() +{ + FragColor = vec4(0.0, 0.0, 0.0, 1.0); +} +)EOF"; + +static const char *shaderOutlineGeometry = R"EOF(#version 330 +layout (points) in; +layout (line_strip, max_vertices = 16) out; + +uniform float size; + +in VS_OUT { + vec4 color; +} gs_in[]; + +out vec4 color; + +#define PI 3.1415926538 + +void main() { + int num = 15; + float theta = 2 * PI / num; + float c = cos(theta); + float s = sin(theta); + vec4 p = vec4(size, 0.0, 0.0, 0.0); + // gs_in[0] as there is only one input vertex + color = gs_in[0].color; + for (int i = 0; i < num; i++) { + gl_Position = gl_in[0].gl_Position + p; + EmitVertex(); + + p.xy = vec2(c*p.x - s*p.y, s*p.x + c*p.y); + } + gl_Position = gl_in[0].gl_Position + p; + EmitVertex(); + EndPrimitive(); +} +)EOF"; + Scatterplot::Scatterplot() : m_glyphSize(DEFAULT_GLYPH_SIZE) , m_colorScale(0) - , m_autoScale(true) + , m_autoScale(false) , m_sx(0, 1, 0, 1) , m_sy(0, 1, 0, 1) , m_anySelected(false) , m_brushedItem(-1) , m_interactionState(StateNone) , m_dragEnabled(false) + , m_redraw(false) , m_shouldUpdateGeometry(false) , m_shouldUpdateMaterials(false) { + glGenFramebuffers(1, &m_FBO); + + glGenBuffers(1, &m_pointsVBO); + glGenBuffers(1, &m_valuesVBO); + glGenVertexArrays(1, &m_VAO); + glBindVertexArray(m_VAO); + glBindBuffer(GL_ARRAY_BUFFER, m_pointsVBO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + // glVertexAttribDivisor(0, 1); + + glBindBuffer(GL_ARRAY_BUFFER, m_valuesVBO); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, nullptr); + glVertexAttribDivisor(1, 1); + glBindVertexArray(0); + + glGenTextures(1, &m_outTex); + + m_shader = std::make_unique( + shaderVertex, + shaderFragment, + shaderGeometry); + + m_shaderOutline = std::make_unique( + shaderVertex, + shaderOutlineFragment, + shaderOutlineGeometry); + + std::fill(&m_transform[0][0], &m_transform[0][0] + 16, 0.0f); + m_transform[3][3] = 1.0f; } -void Scatterplot::setColorScale(std::shared_ptr colorScale) +Scatterplot::~Scatterplot() { - m_colorScale = colorScale; - if (m_colorData.n_elem > 0) { - m_shouldUpdateMaterials = true; - update(); - } + glDeleteFramebuffers(1, &m_FBO); +} + +void Scatterplot::setSize(size_t width, size_t height) +{ + m_width = width; + m_height = height; + + glBindTexture(GL_TEXTURE_2D, m_outTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_width, m_height, 0, GL_RGBA, + GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + update(); +} + +void Scatterplot::setColormap(GLuint texture) +{ + m_colormapTex = texture; + update(); } arma::mat Scatterplot::XY() const @@ -122,8 +291,6 @@ void Scatterplot::setScale(const LinearScale &sx, const LinearScale 0 && m_colorData.n_elem != m_xy.n_rows) { + // Old values are no longer valid, clean up + m_colorData.fill(0.0f); + } + + // Copy 'points' to internal data structure(s) + m_points.resize(m_xy.n_rows); + const double *col_x = m_xy.colptr(0); + const double *col_y = m_xy.colptr(1); + for (unsigned i = 0; i < m_xy.n_rows; i++) { + m_points[i].set(col_x[i], col_y[i]); + } + + glBindBuffer(GL_ARRAY_BUFFER, m_pointsVBO); + glBufferData(GL_ARRAY_BUFFER, m_points.size() * sizeof(vec2), + m_points.data(), GL_DYNAMIC_DRAW); + + m_shouldUpdateGeometry = false; +} + +void Scatterplot::updateMaterials() +{ + glBindBuffer(GL_ARRAY_BUFFER, m_valuesVBO); + glBufferData(GL_ARRAY_BUFFER, m_colorData.n_elem * sizeof(float), + m_colorData.memptr(), GL_DYNAMIC_DRAW); + + m_shouldUpdateMaterials = false; +} + +void Scatterplot::updateTransform() +{ + float padding = Scatterplot::PADDING; + float offsetX = padding / m_width, offsetY = padding / m_height; + updateTransform4x4(m_sx, m_sy, offsetX, offsetY, m_transform); +} + +void Scatterplot::draw() +{ + if (!m_redraw) { + return; + } + + int originalFBO; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &originalFBO); + + glBindFramebuffer(GL_FRAMEBUFFER, m_FBO); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, m_outTex, 0); + + glViewport(0, 0, m_width, m_height); + m_shader->use(); + m_shader->setUniform("transform", m_transform); + m_shader->setUniform("size", m_glyphSize / m_width); + m_shader->setUniform("colormask", m_colorData.n_elem == 0 ? 0.0f : 1.0f); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_colormapTex); + m_shader->setUniform("colormap", 0); + + glEnable(GL_BLEND); + // glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glBindVertexArray(m_VAO); + glDrawArrays(GL_POINTS, 0, m_points.size()); + m_shader->release(); + + // Swap out shaders to draw outline + m_shaderOutline->use(); + m_shaderOutline->setUniform("transform", m_transform); + m_shaderOutline->setUniform("size", m_glyphSize / m_width); + m_shaderOutline->setUniform("colormap", 0); + glDrawArrays(GL_POINTS, 0, m_points.size()); + + glBindVertexArray(0); + m_shaderOutline->release(); + + /* + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + checkGLError("draw():"); + } + */ + + glBindFramebuffer(GL_FRAMEBUFFER, originalFBO); + m_redraw = false; } /* @@ -590,8 +859,8 @@ void Scatterplot::applyManipulation() m_sy.inverse(); m_sx.inverse(); - float tx = m_dragCurrentPos.x() - m_dragOriginPos.x(); - float ty = m_dragCurrentPos.y() - m_dragOriginPos.y(); + float tx = m_dragCurrentPos.x - m_dragOriginPos.x; + float ty = m_dragCurrentPos.y - m_dragOriginPos.y; for (std::vector::size_type i = 0; i < m_selection.size(); i++) { if (m_selection[i]) { @@ -609,10 +878,10 @@ void Scatterplot::applyManipulation() void Scatterplot::updateQuadTree() { - m_sx.setRange(PADDING, width() - PADDING); - m_sy.setRange(height() - PADDING, PADDING); + m_sx.setRange(PADDING, m_width - PADDING); + m_sy.setRange(m_height - PADDING, PADDING); - m_quadtree.reset(new QuadTree(RectF(x(), y(), width(), height()))); + m_quadtree.reset(new QuadTree(RectF(0.0f, 0.0f, m_width, m_height))); for (arma::uword i = 0; i < m_xy.n_rows; i++) { const arma::rowvec &row = m_xy.row(i); m_quadtree->insert(m_sx(row[0]), m_sy(row[1]), (int) i); -- cgit v1.2.3