diff --git a/src/FlatLayerVisualisation.cpp b/src/FlatLayerVisualisation.cpp new file mode 100644 index 0000000..576c625 --- /dev/null +++ b/src/FlatLayerVisualisation.cpp @@ -0,0 +1,93 @@ +#include +#include + +#include "FlatLayerVisualisation.hpp" + +using namespace std; +using namespace fmri; + +static inline void computeColor(float intensity, float limit, float* destination) +{ + const float saturation = min(-log(abs(intensity) / limit) / 10.0f, 1.0f); + if (intensity > 0) { + destination[0] = saturation; + destination[1] = saturation; + destination[2] = 1; + } else { + destination[0] = 1; + destination[1] = saturation; + destination[2] = saturation; + } +} + +FlatLayerVisualisation::FlatLayerVisualisation(const fmri::LayerData &layer) : + faceCount(layer.numEntries() * 4), + vertexBuffer(faceCount * 3), + colorBuffer(faceCount * 3), + indexBuffer(faceCount * 3) +{ + auto& shape = layer.shape(); + CHECK_EQ(shape.size(), 2) << "layer should be flat!" << endl; + CHECK_EQ(shape[0], 1) << "Only single images supported." << endl; + + const int limit = static_cast(layer.numEntries()); + auto data = layer.data(); + const auto [minElem, maxElem] = minmax_element(data, data + limit); + + auto scalingMax = max(abs(*minElem), abs(*maxElem)); + + int v = 0; + int j = 0; + for (int i = 0; i < limit; ++i) { + const float zOffset = -2 * i; + const int vertexBase = i * 4; + // Create the 4 vertices for the pyramid + vertexBuffer[j++] = -0.5f; + vertexBuffer[j++] = 0; + vertexBuffer[j++] = 0.5f + zOffset; + vertexBuffer[j++] = 0; + vertexBuffer[j++] = 0; + vertexBuffer[j++] = -0.5f + zOffset; + vertexBuffer[j++] = 0; + vertexBuffer[j++] = 1; + vertexBuffer[j++] = 0 + zOffset; + vertexBuffer[j++] = 0.5; + vertexBuffer[j++] = 0; + vertexBuffer[j++] = 0.5 + zOffset; + + // Define the colors for the vertices + for (int c = 0; c < 4; ++c) { + computeColor(data[i], scalingMax, &colorBuffer[12 * i + 3 * c]); + } + + // Create the index set for the faces + // Simply connect all vertices in ascending order and it works. + indexBuffer[v++] = vertexBase; + indexBuffer[v++] = vertexBase + 1; + indexBuffer[v++] = vertexBase + 2; + indexBuffer[v++] = vertexBase; + indexBuffer[v++] = vertexBase + 1; + indexBuffer[v++] = vertexBase + 3; + indexBuffer[v++] = vertexBase; + indexBuffer[v++] = vertexBase + 2; + indexBuffer[v++] = vertexBase + 3; + indexBuffer[v++] = vertexBase + 1; + indexBuffer[v++] = vertexBase + 2; + indexBuffer[v++] = vertexBase + 3; + } + assert(v == faceCount * 3); + assert(j == faceCount * 3); +} + +void FlatLayerVisualisation::render() +{ + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + glVertexPointer(3, GL_FLOAT, 0, vertexBuffer.data()); + glColorPointer(3, GL_FLOAT, 0, colorBuffer.data()); + glDrawElements(GL_TRIANGLES, faceCount, GL_UNSIGNED_INT, indexBuffer.data()); + + glDisable(GL_VERTEX_ARRAY); + glDisable(GL_COLOR_ARRAY); +} diff --git a/src/FlatLayerVisualisation.hpp b/src/FlatLayerVisualisation.hpp new file mode 100644 index 0000000..2d72e8e --- /dev/null +++ b/src/FlatLayerVisualisation.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "LayerData.hpp" +#include "LayerVisualisation.hpp" + +namespace fmri +{ + class FlatLayerVisualisation : public LayerVisualisation + { + public: + explicit FlatLayerVisualisation(const LayerData& layer); + + void render() override; + + private: + std::size_t faceCount; + std::vector vertexBuffer; + std::vector colorBuffer; + std::vector indexBuffer; + }; +} diff --git a/src/LayerVisualisation.hpp b/src/LayerVisualisation.hpp new file mode 100644 index 0000000..170bf17 --- /dev/null +++ b/src/LayerVisualisation.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace fmri +{ + class LayerVisualisation + { + public: + virtual ~LayerVisualisation() = default; + + virtual void render() = 0; + }; +} diff --git a/src/MultiImageVisualisation.cpp b/src/MultiImageVisualisation.cpp new file mode 100644 index 0000000..b6dd6d9 --- /dev/null +++ b/src/MultiImageVisualisation.cpp @@ -0,0 +1,37 @@ +#include +#include "MultiImageVisualisation.hpp" +#include "glutils.hpp" + +using namespace fmri; +using namespace std; + +MultiImageVisualisation::MultiImageVisualisation(const fmri::LayerData &layer) +{ + auto dimensions = layer.shape(); + CHECK_EQ(4, dimensions.size()) << "Should be image-like layer"; + + const auto images = dimensions[0], + channels = dimensions[1], + width = dimensions[2], + height = dimensions[3]; + + auto dataPtr = layer.data(); + for (auto i = 0; i < images; ++i) { + for (auto j = 0; j < channels; ++j) { + textureReferences[make_pair(i, j)] = loadTexture(dataPtr, width, height); + dataPtr += width * height; + } + } +} + +MultiImageVisualisation::~MultiImageVisualisation() +{ + for (auto entry : textureReferences) { + glDeleteTextures(0, &entry.second); + } +} + +void MultiImageVisualisation::render() +{ + // TODO: do something. +} diff --git a/src/MultiImageVisualisation.hpp b/src/MultiImageVisualisation.hpp new file mode 100644 index 0000000..b1a46c3 --- /dev/null +++ b/src/MultiImageVisualisation.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include "LayerVisualisation.hpp" +#include "LayerData.hpp" + +namespace fmri +{ + class MultiImageVisualisation : public LayerVisualisation + { + public: + explicit MultiImageVisualisation(const LayerData&); + ~MultiImageVisualisation() override; + + void render() override; + + private: + std::map, GLuint> textureReferences; + }; +} diff --git a/src/camera.cpp b/src/camera.cpp index 57d7daf..6cb3db2 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -15,6 +15,8 @@ static void handleMouseMove(int x, int y) camera.angle[0] = (x - width) / width * 180; camera.angle[1] = (y - height) / height * 90; + + glutPostRedisplay(); } static void move(unsigned char key) @@ -63,6 +65,8 @@ static void handleKeys(unsigned char key, int, int) // Do nothing. break; } + + glutPostRedisplay(); } std::string Camera::infoLine() diff --git a/src/glutils.cpp b/src/glutils.cpp index c86a2ce..4e521e4 100644 --- a/src/glutils.cpp +++ b/src/glutils.cpp @@ -103,8 +103,11 @@ GLuint fmri::compileShader(GLenum type, char const *source) } } -void ::fmri::setColorFromIntensity(float i) +void fmri::renderText(std::string_view text) { - // TODO: something more expressive. - glColor3f(i, i, i); + constexpr auto font = GLUT_BITMAP_HELVETICA_10; + glRasterPos2i(0, 0); + for (char c : text) { + glutBitmapCharacter(font, c); + } } diff --git a/src/glutils.hpp b/src/glutils.hpp index 3fedb87..a8c3561 100644 --- a/src/glutils.hpp +++ b/src/glutils.hpp @@ -3,6 +3,7 @@ #include "LayerData.hpp" #include "utils.hpp" #include +#include namespace fmri { /** @@ -35,9 +36,9 @@ namespace fmri { void changeWindowSize(int w, int h); /** - * Set the current drawing color based on some intensity value. + * Draw a bitmap string at the current location. * - * @param i The intensity. + * @param text The text to draw. */ - void setColorFromIntensity(float i); + void renderText(std::string_view text); } diff --git a/src/main.cpp b/src/main.cpp index b70e5c4..027488c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,9 @@ #include "Simulator.hpp" #include "glutils.hpp" #include "camera.hpp" +#include "LayerVisualisation.hpp" +#include "FlatLayerVisualisation.hpp" +#include "MultiImageVisualisation.hpp" using namespace std; using namespace fmri; @@ -21,6 +24,7 @@ struct float angle = 0; vector* currentData = nullptr; map, GLuint> textureMap; + vector> layerVisualisations; } rendererData; static vector> getSimulationData(const Options &options) @@ -44,7 +48,7 @@ static vector> getSimulationData(const Options &options) return results; } -static void renderLayer(const LayerData& data); +static void renderLayerName(const LayerData &data); static void render() { @@ -55,11 +59,16 @@ static void render() camera.configureRenderingContext(); + const auto& dataSet = *rendererData.currentData; + glPushMatrix(); - glTranslatef(5 * rendererData.currentData->size(), 0, 0); - for (auto& layer : *rendererData.currentData) { + glTranslatef(5 * dataSet.size(), 0, 0); + + for (unsigned int i = 0; i < dataSet.size(); ++i) { glPushMatrix(); - renderLayer(layer); + renderLayerName(dataSet[i]); + rendererData.layerVisualisations[i]->render(); + glPopMatrix(); glTranslatef(-10, 0, 0); } @@ -68,110 +77,36 @@ static void render() glutSwapBuffers(); } -static void drawOneParticle() +static void renderLayerName(const LayerData &data) { - // Code taken from CG workshop 1. Should probably replace with something nicer. - glBegin(GL_TRIANGLE_STRIP); - // triangle 1 - glVertex3f(-0.5, 0.0, 0.5); // A - glVertex3f(0.0, 0.0, -0.5); // B - glVertex3f(0.0, 1.0, 0.0); // top - // triangle 2 - glVertex3f(0.5, 0.0, 0.5); // C - // triangle 3 - glVertex3f(-0.5, 0.0, 0.5); // A again - // triangle 4 (bottom) - glVertex3f(0.0, 0.0, -0.5); // B again - glEnd(); -} - -static void renderFlatLayer(const LayerData& data) -{ - const auto [minElem,maxElem] = minmax_element(data.data(), data.data() + data.numEntries()); - const float intensityLimit = max(abs(*minElem), abs(*maxElem)); - auto& shape = data.shape(); - CHECK_EQ(shape[0], 1) << "Should have only one instance per layer." << endl; - // Draw one triangle for every point in the layer - // Color depends on current value. - vector intensities(data.data(), data.data() + data.numEntries()); - transform(intensities.begin(), intensities.end(), intensities.begin(), [=](auto x) { return clamp(x, -intensityLimit, intensityLimit);}); - - glPushMatrix(); - for (auto i : intensities) { - auto intensity = min(-log(abs(i) / intensityLimit) / 10.0f, 1.0f); - if (i > 0) { - glColor3f(intensity, intensity, 1); - } else { - glColor3f(1, intensity, intensity); - } - drawOneParticle(); - glTranslatef(0, 0, -2); - } - - glPopMatrix(); - - -} - -static void renderText(string_view text) -{ - constexpr auto font = GLUT_BITMAP_HELVETICA_10; - glRasterPos2i(0, 0); - for (char c : text) { - glutBitmapCharacter(font, c); - } -} - -static void renderLayer(const LayerData& data) -{ - auto& shape = data.shape(); // Draw the name of the layer for reference. glColor3f(0.5, 0.5, 0.5); renderText(data.name()); glTranslatef(0, 0, -10); - switch (shape.size()) { - case 4: - // TODO: implement this. - return; - - case 2: - renderFlatLayer(data); - return; - - default: - cerr << "What is this even: " << data << endl; - } } -static void reloadTextures(unsigned dataIndex) +static void updateVisualisers(unsigned int dataset) { - // First, release any existing textures - for (auto &entry : rendererData.textureMap) { - glDeleteTextures(0, &entry.second); - } - - rendererData.textureMap.clear(); - rendererData.currentData = &rendererData.data[dataIndex]; + rendererData.layerVisualisations.clear(); + rendererData.currentData = &rendererData.data[dataset]; for (auto &layer : *rendererData.currentData) { - auto dimensions = layer.shape(); - if (dimensions.size() != 4) { - continue; + LayerVisualisation* visualisation = nullptr; + switch (layer.shape().size()) { + case 2: + visualisation = new FlatLayerVisualisation(layer); + break; + + case 4: + visualisation = new MultiImageVisualisation(layer); + break; + + default: + abort(); } - const auto images = dimensions[0], - channels = dimensions[1], - width = dimensions[2], - height = dimensions[3]; - - auto dataPtr = layer.data(); - for (auto i = 0; i < images; ++i) { - for (auto j = 0; j < channels; ++j) { - rendererData.textureMap[make_tuple(layer.name(), i, j)] = loadTexture(dataPtr, width, height); - dataPtr += width * height; - } - } + rendererData.layerVisualisations.emplace_back(visualisation); } } @@ -191,7 +126,7 @@ int main(int argc, char *argv[]) // Register callbacks glutDisplayFunc(render); - glutIdleFunc(render); + glutIdleFunc(glutPostRedisplay); glutReshapeFunc(changeWindowSize); Camera::instance().registerControls(); @@ -202,7 +137,7 @@ int main(int argc, char *argv[]) return 2; } - reloadTextures(0); + updateVisualisers(0); // Enable depth test to fix objects behind you glEnable(GL_DEPTH_TEST);