aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Fadel <samuelfadel@gmail.com>2015-12-19 22:11:33 +0100
committerSamuel Fadel <samuelfadel@gmail.com>2015-12-19 22:11:33 +0100
commit841ede5c34a5cb001b540ed13a8439dc8df7d2a9 (patch)
tree11970d698ded9c6353895eca37ca662241edda4b
parente20c03905d6f8418963bd3ce63040e2fe571a6da (diff)
Experimenting using VoronoiSplat as a texture.
-rw-r--r--main.cpp14
-rw-r--r--main_view.qml14
-rw-r--r--scatterplot.cpp118
-rw-r--r--scatterplot.h4
-rw-r--r--voronoisplat.cpp326
-rw-r--r--voronoisplat.h91
6 files changed, 290 insertions, 277 deletions
diff --git a/main.cpp b/main.cpp
index 64560d3..b9d7232 100644
--- a/main.cpp
+++ b/main.cpp
@@ -157,7 +157,6 @@ int main(int argc, char **argv)
m->setSubsample(Ys);
qmlRegisterType<Scatterplot>("PM", 1, 0, "Scatterplot");
- qmlRegisterType<VoronoiSplat>("PM", 1, 0, "VoronoiSplat");
qmlRegisterType<HistoryGraph>("PM", 1, 0, "HistoryGraph");
qmlRegisterType<InteractionHandler>("PM", 1, 0, "InteractionHandler");
qmlRegisterSingletonType<Main>("PM", 1, 0, "Main", mainProvider);
@@ -190,8 +189,7 @@ int main(int argc, char **argv)
// subsamplePlot->setColorData(arma::zeros<arma::vec>(subsampleSize));
subsamplePlot->setColorScale(&colorScale);
Scatterplot *plot = engine.rootObjects()[0]->findChild<Scatterplot *>("plot");
- VoronoiSplat *splat = engine.rootObjects()[0]->findChild<VoronoiSplat *>("splat");
- skelft2DInitialization(splat->width());
+ skelft2DInitialization(plot->width());
// Keep track of the current subsample (in order to save them later, if requested)
QObject::connect(subsamplePlot, SIGNAL(xyChanged(const arma::mat &)),
@@ -211,10 +209,10 @@ int main(int argc, char **argv)
m->setTechnique(InteractionHandler::TECHNIQUE_LAMP);
// Update splat whenever the main plot is also updated
- QObject::connect(plot, SIGNAL(xyChanged(const arma::mat &)),
- splat, SLOT(setPoints(const arma::mat &)));
- QObject::connect(plot, SIGNAL(colorDataChanged(const arma::vec &)),
- splat, SLOT(setValues(const arma::vec &)));
+ //QObject::connect(plot, SIGNAL(xyChanged(const arma::mat &)),
+ // splat, SLOT(setPoints(const arma::mat &)));
+ //QObject::connect(plot, SIGNAL(colorDataChanged(const arma::vec &)),
+ // splat, SLOT(setValues(const arma::vec &)));
// Linking between selections in subsample plot and full dataset plot
SelectionHandler selectionHandler(sampleIndices);
@@ -252,7 +250,7 @@ int main(int argc, char **argv)
subsamplePlot->setXY(Ys);
subsamplePlot->setColorData(labels(sampleIndices));
plot->setColorScale(&colorScale);
- splat->setColorScale(&colorScale);
+ //splat->setColorScale(&colorScale);
plot->setColorData(labels);
auto ret = app.exec();
diff --git a/main_view.qml b/main_view.qml
index 62c3a67..9e43137 100644
--- a/main_view.qml
+++ b/main_view.qml
@@ -9,7 +9,7 @@ ApplicationWindow {
id: mainWindow
title: "Projection Manipulation"
visible: true
- minimumWidth: 900
+ minimumWidth: 300
minimumHeight: 600
maximumWidth: minimumWidth
maximumHeight: minimumHeight
@@ -91,18 +91,12 @@ ApplicationWindow {
border.width: 1
border.color: "#cccccc"
- VoronoiSplat {
- id: splat
- objectName: "splat"
- width: 256
- height: 256
- }
-
Scatterplot {
id: plot
objectName: "plot"
- anchors.fill: parent
- visible: false
+ width: 512
+ height: 512
+ anchors.margins: 10
}
}
}
diff --git a/scatterplot.cpp b/scatterplot.cpp
index 10e9765..b9d7aea 100644
--- a/scatterplot.cpp
+++ b/scatterplot.cpp
@@ -1,5 +1,6 @@
#include "scatterplot.h"
+#include "voronoisplat.h"
#include "geometry.h"
#include <cmath>
@@ -104,6 +105,26 @@ void Scatterplot::updateMaterials()
update();
}
+QSGNode *Scatterplot::createSplatNode()
+{
+ if (m_xy.n_rows < 1) {
+ return 0;
+ }
+
+ QSGSimpleTextureNode *node = new QSGSimpleTextureNode;
+ VoronoiSplatTexture *tex = new VoronoiSplatTexture(QSize(width(), height()));
+ tex->setSites(m_xy);
+ tex->setValues(m_colorData);
+ tex->setColormap(m_colorScale);
+ tex->updateTexture();
+ window()->resetOpenGLState();
+ node->setTexture(tex);
+ node->setOwnsTexture(true);
+ node->setRect(x(), y(), width(), height());
+ node->setSourceRect(0, 0, width(), height());
+ return node;
+}
+
QSGNode *Scatterplot::createGlyphNodeTree()
{
if (m_xy.n_rows < 1) {
@@ -153,6 +174,10 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
QSGNode *root = 0;
if (!oldNode) {
root = new QSGNode;
+ QSGNode *splatNode = createSplatNode();
+ if (splatNode) {
+ root->appendChildNode(splatNode);
+ }
QSGNode *glyphTreeRoot = createGlyphNodeTree();
if (glyphTreeRoot) {
root->appendChildNode(glyphTreeRoot);
@@ -165,6 +190,67 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
return root;
}
+ QSGNode *splatNode = root->firstChild();
+ updateSplat(splatNode);
+
+ QSGNode *glyphsRootNode = root->firstChild()->nextSibling()->firstChild();
+ updateGlyphs(glyphsRootNode);
+
+ if (m_shouldUpdateGeometry) {
+ m_shouldUpdateGeometry = false;
+ }
+ if (m_shouldUpdateMaterials) {
+ m_shouldUpdateMaterials = false;
+ }
+
+ // Selection rect
+ if (m_currentInteractionState == INTERACTION_SELECTING) {
+ QSGSimpleRectNode *selectionNode = 0;
+ if (!root->firstChild()->nextSibling()->nextSibling()) {
+ selectionNode = new QSGSimpleRectNode;
+ selectionNode->setColor(SELECTION_COLOR);
+ root->appendChildNode(selectionNode);
+ } else {
+ selectionNode = static_cast<QSGSimpleRectNode *>(root->firstChild()->nextSibling()->nextSibling());
+ }
+
+ selectionNode->setRect(QRectF(m_dragOriginPos, m_dragCurrentPos));
+ selectionNode->markDirty(QSGNode::DirtyGeometry);
+ } else {
+ QSGNode *node = root->firstChild()->nextSibling()->nextSibling();
+ if (node) {
+ root->removeChildNode(node);
+ delete node;
+ }
+ }
+
+ animationTick();
+ return root;
+}
+
+void Scatterplot::updateSplat(QSGNode *node)
+{
+ QSGSimpleTextureNode *texNode = static_cast<QSGSimpleTextureNode *>(node);
+ VoronoiSplatTexture *tex =
+ static_cast<VoronoiSplatTexture *>(texNode->texture());
+
+ if (m_shouldUpdateGeometry) {
+ tex->setSites(m_xy);
+ }
+
+ if (m_shouldUpdateMaterials) {
+ tex->setValues(m_colorData);
+ tex->setColormap(m_colorScale);
+ }
+
+ if (tex->updateTexture()) {
+ window()->resetOpenGLState();
+ }
+}
+
+void Scatterplot::updateGlyphs(QSGNode *node)
+{
+
qreal x, y, tx, ty, moveTranslationF;
if (m_currentInteractionState == INTERACTION_MOVING) {
@@ -177,7 +263,6 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
m_sx.setRange(PADDING, width() - PADDING);
m_sy.setRange(height() - PADDING, PADDING);
- QSGNode *node = root->firstChild()->firstChild();
float t = m_animationEasing.valueForProgress(m_t);
for (arma::uword i = 0; i < m_xy.n_rows; i++) {
const arma::rowvec &oldRow = m_oldXY.row(i);
@@ -211,37 +296,6 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
node = node->nextSibling();
}
-
- if (m_shouldUpdateGeometry) {
- m_shouldUpdateGeometry = false;
- }
- if (m_shouldUpdateMaterials) {
- m_shouldUpdateMaterials = false;
- }
-
- // Selection rect
- if (m_currentInteractionState == INTERACTION_SELECTING) {
- QSGSimpleRectNode *selectionNode = 0;
- if (!root->firstChild()->nextSibling()) {
- selectionNode = new QSGSimpleRectNode;
- selectionNode->setColor(SELECTION_COLOR);
- root->appendChildNode(selectionNode);
- } else {
- selectionNode = static_cast<QSGSimpleRectNode *>(root->firstChild()->nextSibling());
- }
-
- selectionNode->setRect(QRectF(m_dragOriginPos, m_dragCurrentPos));
- selectionNode->markDirty(QSGNode::DirtyGeometry);
- } else {
- node = root->firstChild()->nextSibling();
- if (node) {
- root->removeChildNode(node);
- delete node;
- }
- }
-
- animationTick();
- return root;
}
void Scatterplot::resetAnimation()
diff --git a/scatterplot.h b/scatterplot.h
index 255649a..2887752 100644
--- a/scatterplot.h
+++ b/scatterplot.h
@@ -38,6 +38,7 @@ protected:
void mouseReleaseEvent(QMouseEvent *event);
private:
+ QSGNode *createSplatNode();
QSGNode *createGlyphNodeTree();
bool updateSelection(bool mergeSelection);
@@ -46,6 +47,9 @@ private:
void updateGeometry();
void updateMaterials();
+ void updateSplat(QSGNode *node);
+ void updateGlyphs(QSGNode *node);
+
void resetAnimation();
void startAnimation();
void animationTick();
diff --git a/voronoisplat.cpp b/voronoisplat.cpp
index da30d3c..7695d97 100644
--- a/voronoisplat.cpp
+++ b/voronoisplat.cpp
@@ -10,18 +10,33 @@
static const float RAD_BLUR = 5.0f;
static const int COLORMAP_SAMPLES = 128;
+static const float PADDING = 10.0f; // in screen pixels
-VoronoiSplatRenderer::VoronoiSplatRenderer(const QSize &size)
- : m_item(0)
- , gl(QOpenGLContext::currentContext())
- , m_size(size)
+static int nextPow2(int n)
{
+ // TODO: check for overflows
+ n--;
+ for (int shift = 1; ((n + 1) & n); shift <<= 1) {
+ n |= n >> shift;
+ }
+ return n + 1;
+}
+
+VoronoiSplatTexture::VoronoiSplatTexture(const QSize &size)
+ : gl(QOpenGLContext::currentContext())
+ , m_cmap(3*COLORMAP_SAMPLES)
+{
+ int baseSize = nextPow2(std::min(size.width(), size.height()));
+ m_size.setWidth(baseSize);
+ m_size.setHeight(baseSize);
+
+ gl.glGenFramebuffers(1, &m_FBO);
setupShaders();
setupVAOs();
setupTextures();
}
-void VoronoiSplatRenderer::setupShaders()
+void VoronoiSplatTexture::setupShaders()
{
m_program1 = new QOpenGLShaderProgram;
m_program1->addShaderFromSourceCode(QOpenGLShader::Vertex,
@@ -116,7 +131,7 @@ void main() {
m_program2->link();
}
-void VoronoiSplatRenderer::setupVAOs()
+void VoronoiSplatTexture::setupVAOs()
{
gl.glGenBuffers(3, m_VBOs);
@@ -148,7 +163,7 @@ void VoronoiSplatRenderer::setupVAOs()
m_2ndPassVAO.release();
}
-void VoronoiSplatRenderer::setupTextures()
+void VoronoiSplatTexture::setupTextures()
{
gl.glGenTextures(2, m_textures);
@@ -167,162 +182,59 @@ void VoronoiSplatRenderer::setupTextures()
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Used for colormap lookup in the frag shader
- gl.glGenTextures(1, &m_colorMapTex);
- gl.glBindTexture(GL_TEXTURE_2D, m_colorMapTex);
+ gl.glGenTextures(1, &m_colormapTex);
+ gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex);
gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, COLORMAP_SAMPLES, 1, 0, GL_RGB,
GL_FLOAT, 0);
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ // The output texture
+ gl.glGenTextures(1, &m_tex);
+ gl.glBindTexture(GL_TEXTURE_2D, m_tex);
+ gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_size.width(),
+ m_size.height(), 0, GL_RGBA, GL_FLOAT, 0);
+ gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
-VoronoiSplatRenderer::~VoronoiSplatRenderer()
+VoronoiSplatTexture::~VoronoiSplatTexture()
{
gl.glDeleteBuffers(3, m_VBOs);
gl.glDeleteTextures(2, m_textures);
- gl.glDeleteTextures(1, &m_colorMapTex);
+ gl.glDeleteTextures(1, &m_colormapTex);
+ gl.glDeleteTextures(1, &m_tex);
+
+ gl.glDeleteFramebuffers(1, &m_FBO);
delete m_program1;
delete m_program2;
}
-void VoronoiSplatRenderer::setSites(const arma::mat &points)
-{
- if (points.n_rows < 1 || points.n_cols != 2
- || (m_values.size() != 0 && points.n_rows != m_values.size())) {
- return;
- }
-
- // Copy 'points' to internal data structure(s)
- copyPoints(points);
-
- // Update VBO with the new data
- gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[0]);
- gl.glBufferData(GL_ARRAY_BUFFER, m_sites.size() * sizeof(float),
- m_sites.data(), GL_STATIC_DRAW);
-
- // Compute DT values for the new positions
- computeDT();
-}
-
-void VoronoiSplatRenderer::copyPoints(const arma::mat &points)
-{
- double minX = points.col(0).min();
- double maxX = points.col(0).max();
- double minY = points.col(1).min();
- double maxY = points.col(1).max();
-
- // Coords are tighly packed into 'm_sites' as [ (x1, y1), (x2, y2), ... ]
- m_sites.resize(2*points.n_rows);
- LinearScale<float> sx(minX, maxX, 1, m_size.width() - 1);
- const double *col = points.colptr(0);
- for (unsigned i = 0; i < points.n_rows; i++) {
- m_sites[2*i] = sx(col[i]);
- }
-
- col = points.colptr(1);
- LinearScale<float> sy(minY, maxY, m_size.height() - 1, 1);
- for (unsigned i = 0; i < points.n_rows; i++) {
- m_sites[2*i + 1] = sy(col[i]);
- }
-}
-
-void VoronoiSplatRenderer::computeDT()
-{
- int w = m_size.width(), h = m_size.height();
-
- std::vector<float> buf(w*h, 0.0f);
- for (unsigned i = 0; i < m_sites.size(); i += 2) {
- buf[int(m_sites[i + 1])*h + int(m_sites[i])] = (float) i/2 + 1;
- }
-
- // Compute FT of the sites
- skelft2DFT(0, buf.data(), 0, 0, w, h, w);
- // Compute DT of the sites (from the resident FT)
- skelft2DDT(buf.data(), 0, 0, w, h);
-
- std::vector<GLfloat> dtTexData(2*w*h, 0.0f);
- for (unsigned i = 0; i < buf.size(); i++) {
- dtTexData[2*i] = buf[i];
- }
-
- // Upload result to lookup texture
- gl.glBindTexture(GL_TEXTURE_2D, m_textures[0]);
- gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RG, GL_FLOAT,
- dtTexData.data());
-}
-
-void VoronoiSplatRenderer::setValues(const arma::vec &values)
+void VoronoiSplatTexture::bind()
{
- if (values.n_elem == 0
- || (m_sites.size() != 0 && values.n_elem != m_sites.size() / 2)) {
- return;
- }
-
- m_values.resize(values.n_elem);
- LinearScale<float> scale(values.min(), values.max(), 0, 1.0f);
- std::transform(values.begin(), values.end(), m_values.begin(), scale);
-
- // Update VBO with the new data
- gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[1]);
- gl.glBufferData(GL_ARRAY_BUFFER, m_values.size() * sizeof(float),
- m_values.data(), GL_DYNAMIC_DRAW);
+ gl.glBindTexture(GL_TEXTURE_2D, m_tex);
}
-void VoronoiSplatRenderer::setColorMap(const ColorScale *scale)
+bool VoronoiSplatTexture::updateTexture()
{
- if (!scale) {
- return;
+ if (!m_sitesUpdated && !m_valuesUpdated && !m_colormapUpdated) {
+ // Texture is already updated
+ return false;
}
- float t = scale->min();
- float step = (scale->max() - scale->min()) / COLORMAP_SAMPLES;
- qreal r, g, b;
- std::vector<float> cmap(3*COLORMAP_SAMPLES); // R,G,B
- for (int i = 0; i < 3*COLORMAP_SAMPLES; i += 3) {
- scale->color(t).getRgbF(&r, &g, &b);
- cmap[i + 0] = r;
- cmap[i + 1] = g;
- cmap[i + 2] = b;
-
- t += step;
- }
-
- // Update texture data
- gl.glBindTexture(GL_TEXTURE_2D, m_colorMapTex);
- gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, COLORMAP_SAMPLES, 1, GL_RGB,
- GL_FLOAT, cmap.data());
-}
-
-QOpenGLFramebufferObject *VoronoiSplatRenderer::createFramebufferObject(const QSize &size)
-{
- return new QOpenGLFramebufferObject(size);
-}
-
-void VoronoiSplatRenderer::synchronize(QQuickFramebufferObject *item)
-{
- m_item = static_cast<VoronoiSplat *>(item);
-
- if (m_item->colorScaleUpdated()) {
- setColorMap(m_item->colorScale());
+ // Update OpenGL buffers and textures as needed
+ if (m_sitesUpdated) {
+ updateSites();
}
- if (m_item->pointsUpdated()) {
- setSites(m_item->points());
+ if (m_valuesUpdated) {
+ updateValues();
}
- if (m_item->valuesUpdated()) {
- setValues(m_item->values());
+ if (m_colormapUpdated) {
+ updateColormap();
}
- m_item->setUpdated(false);
-}
-
-void VoronoiSplatRenderer::render()
-{
- // Store the final texture to render to, as we use a two-pass rendering
- // which first renders to an intermediate texture
- GLint targetTexture;
- gl.glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
- GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
- &targetTexture);
+ gl.glBindFramebuffer(GL_FRAMEBUFFER, m_FBO);
// The first pass
m_program1->bind();
@@ -364,14 +276,14 @@ void VoronoiSplatRenderer::render()
gl.glBindTexture(GL_TEXTURE_2D, m_textures[1]);
m_program2->setUniformValue("accumTex", 1);
gl.glActiveTexture(GL_TEXTURE2);
- gl.glBindTexture(GL_TEXTURE_2D, m_colorMapTex);
+ gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex);
m_program2->setUniformValue("colormap", 2);
gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- // Restore the original framebuffer texture
+ // Now we render to the output texture
gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_2D, targetTexture, 0);
+ GL_TEXTURE_2D, m_tex, 0);
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
@@ -382,31 +294,127 @@ void VoronoiSplatRenderer::render()
m_program2->release();
- m_item->window()->resetOpenGLState();
+ return true;
}
-static int nextPow2(int n)
+void VoronoiSplatTexture::updateSites()
{
- // TODO: check for overflows
- n--;
- for (int shift = 1; ((n + 1) & n); shift <<= 1) {
- n |= n >> shift;
+ // Update VBO with the new data
+ gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[0]);
+ gl.glBufferData(GL_ARRAY_BUFFER, m_sites.size() * sizeof(float),
+ m_sites.data(), GL_STATIC_DRAW);
+
+ // Compute DT values for the new positions
+ computeDT();
+
+ m_sitesUpdated = false;
+}
+
+void VoronoiSplatTexture::updateValues()
+{
+ // Update VBO with the new data
+ gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[1]);
+ gl.glBufferData(GL_ARRAY_BUFFER, m_values.size() * sizeof(float),
+ m_values.data(), GL_DYNAMIC_DRAW);
+
+ m_valuesUpdated = false;
+}
+
+void VoronoiSplatTexture::updateColormap()
+{
+ gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex);
+ gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, COLORMAP_SAMPLES, 1, GL_RGB,
+ GL_FLOAT, m_cmap.data());
+
+ m_colormapUpdated = false;
+}
+
+void VoronoiSplatTexture::computeDT()
+{
+ int w = m_size.width(), h = m_size.height();
+
+ std::vector<float> buf(w*h, 0.0f);
+ for (unsigned i = 0; i < m_sites.size(); i += 2) {
+ buf[int(m_sites[i + 1])*h + int(m_sites[i])] = (float) i/2 + 1;
}
- return n + 1;
+
+ // Compute FT of the sites
+ skelft2DFT(0, buf.data(), 0, 0, w, h, w);
+ // Compute DT of the sites (from the resident FT)
+ skelft2DDT(buf.data(), 0, 0, w, h);
+
+ std::vector<GLfloat> dtTexData(2*w*h, 0.0f);
+ for (unsigned i = 0; i < buf.size(); i++) {
+ dtTexData[2*i] = buf[i];
+ }
+
+ // Upload result to lookup texture
+ gl.glBindTexture(GL_TEXTURE_2D, m_textures[0]);
+ gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RG, GL_FLOAT,
+ dtTexData.data());
+}
+
+void VoronoiSplatTexture::setSites(const arma::mat &points)
+{
+ if (points.n_rows < 1 || points.n_cols != 2
+ || (m_values.size() != 0 && points.n_rows != m_values.size())) {
+ return;
+ }
+
+ // Copy 'points' to internal data structure(s)
+ double minX = points.col(0).min();
+ double maxX = points.col(0).max();
+ double minY = points.col(1).min();
+ double maxY = points.col(1).max();
+
+ // Coords are tighly packed into 'm_sites' as [ (x1, y1), (x2, y2), ... ]
+ m_sites.resize(2*points.n_rows);
+ LinearScale<float> sx(minX, maxX, PADDING, m_size.width() - PADDING);
+ const double *col = points.colptr(0);
+ for (unsigned i = 0; i < points.n_rows; i++) {
+ m_sites[2*i] = sx(col[i]);
+ }
+
+ col = points.colptr(1);
+ LinearScale<float> sy(minY, maxY, m_size.height() - PADDING, PADDING);
+ for (unsigned i = 0; i < points.n_rows; i++) {
+ m_sites[2*i + 1] = sy(col[i]);
+ }
+
+ m_sitesUpdated = true;
}
-VoronoiSplat::VoronoiSplat(QQuickItem *parent)
- : QQuickFramebufferObject(parent)
- , m_colorScaleUpdated(false)
- , m_pointsUpdated(false)
- , m_valuesUpdated(false)
- , m_colorScale(0)
+void VoronoiSplatTexture::setValues(const arma::vec &values)
{
- setTextureFollowsItemSize(false);
+ if (values.n_elem == 0
+ || (m_sites.size() != 0 && values.n_elem != m_sites.size() / 2)) {
+ return;
+ }
+
+ m_values.resize(values.n_elem);
+ LinearScale<float> scale(values.min(), values.max(), 0, 1.0f);
+ std::transform(values.begin(), values.end(), m_values.begin(), scale);
+
+ m_valuesUpdated = true;
}
-QQuickFramebufferObject::Renderer *VoronoiSplat::createRenderer() const
+void VoronoiSplatTexture::setColormap(const ColorScale *scale)
{
- int baseSize = nextPow2(std::min(width(), height()));
- return new VoronoiSplatRenderer(QSize(baseSize, baseSize));
+ if (!scale) {
+ return;
+ }
+
+ float t = scale->min();
+ float step = (scale->max() - scale->min()) / COLORMAP_SAMPLES;
+ qreal r, g, b;
+ for (unsigned i = 0; i < m_cmap.size(); i += 3) {
+ scale->color(t).getRgbF(&r, &g, &b);
+ m_cmap[i + 0] = r;
+ m_cmap[i + 1] = g;
+ m_cmap[i + 2] = b;
+
+ t += step;
+ }
+
+ m_colormapUpdated = true;
}
diff --git a/voronoisplat.h b/voronoisplat.h
index 5913218..78dfd3e 100644
--- a/voronoisplat.h
+++ b/voronoisplat.h
@@ -1,26 +1,31 @@
#ifndef VORONOISPLAT_H
#define VORONOISPLAT_H
-#include <QQuickFramebufferObject>
+#include <QSGDynamicTexture>
#include <QOpenGLFunctions>
-#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <armadillo>
#include "colorscale.h"
-class VoronoiSplat; // defined after this class
-
-class VoronoiSplatRenderer
- : public QQuickFramebufferObject::Renderer
+class VoronoiSplatTexture
+ : public QSGDynamicTexture
{
public:
// 'size' must be square (and power of 2)
- VoronoiSplatRenderer(const QSize &size);
- ~VoronoiSplatRenderer();
+ VoronoiSplatTexture(const QSize &size);
+ virtual ~VoronoiSplatTexture();
+
+ void bind();
- void synchronize(QQuickFramebufferObject *item);
+ // When true is returned, should probably call resetOpenGLState() on window
+ bool updateTexture();
+
+ bool hasAlphaChannel() const { return true; }
+ bool hasMipmaps() const { return false; }
+ int textureId() const { return m_tex; }
+ QSize textureSize() const { return m_size; }
// 'points' should be a 2D points matrix (each point in a row)
void setSites(const arma::mat &points);
@@ -29,79 +34,29 @@ public:
void setValues(const arma::vec &values);
// Set colormap data based on the given color scale;
- void setColorMap(const ColorScale *scale);
-
- QOpenGLFramebufferObject *createFramebufferObject(const QSize &size);
- void render();
+ void setColormap(const ColorScale *scale);
private:
void setupShaders();
void setupVAOs();
void setupTextures();
- void copyPoints(const arma::mat &points);
+
+ void updateSites();
+ void updateValues();
+ void updateColormap();
void computeDT();
- VoronoiSplat *m_item;
QOpenGLFunctions gl;
QOpenGLShaderProgram *m_program1, *m_program2;
+ GLuint m_FBO;
GLuint m_VBOs[3];
- GLuint m_textures[2], m_colorMapTex;
+ GLuint m_textures[2], m_colormapTex, m_tex;
QOpenGLVertexArrayObject m_sitesVAO, m_2ndPassVAO;
- std::vector<float> m_sites, m_values;
+ std::vector<float> m_sites, m_values, m_cmap;
QSize m_size;
-};
-
-class VoronoiSplat
- : public QQuickFramebufferObject
-{
- Q_OBJECT
-public:
- VoronoiSplat(QQuickItem *parent = 0);
- Renderer *createRenderer() const;
-
- const arma::mat &points() const { return m_points; }
- const arma::vec &values() const { return m_values; }
- const ColorScale *colorScale() const { return m_colorScale; }
-
- bool colorScaleUpdated() const { return m_colorScaleUpdated; }
- bool pointsUpdated() const { return m_pointsUpdated; }
- bool valuesUpdated() const { return m_valuesUpdated; }
-
-public slots:
- void setColorScale(const ColorScale *scale)
- {
- m_colorScale = scale;
- m_colorScaleUpdated = true;
- update();
- }
- void setPoints(const arma::mat &points)
- {
- m_points = points;
- m_pointsUpdated = true;
- update();
- }
-
- void setValues(const arma::vec &values)
- {
- m_values = values;
- m_valuesUpdated = true;
- update();
- }
-
- void setUpdated(bool updated)
- {
- m_colorScaleUpdated = updated;
- m_pointsUpdated = updated;
- m_valuesUpdated = updated;
- }
-
-private:
- bool m_colorScaleUpdated, m_pointsUpdated, m_valuesUpdated;
- const ColorScale *m_colorScale;
- arma::mat m_points;
- arma::vec m_values;
+ bool m_sitesUpdated, m_valuesUpdated, m_colormapUpdated;
};
#endif // VORONOISPLAT_H