Created
February 5, 2019 10:59
-
-
Save franko/6bf6642b811066c36db3a3a55fc34359 to your computer and use it in GitHub Desktop.
C++ Magnum App trying to render text
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cmath> | |
#include <utility> | |
#include <Corrade/Utility/Format.h> | |
#include <Corrade/PluginManager/Manager.h> | |
#include <Magnum/GL/Buffer.h> | |
#include <Magnum/GL/DefaultFramebuffer.h> | |
#include <Magnum/GL/Mesh.h> | |
#include <Magnum/GL/Renderer.h> | |
#include <Magnum/MeshTools/Interleave.h> | |
#include <Magnum/MeshTools/CompressIndices.h> | |
#include <Magnum/Platform/Sdl2Application.h> | |
#include <Magnum/Shaders/Phong.h> | |
#include <Magnum/Shaders/Flat.h> | |
#include <Magnum/Trade/MeshData3D.h> | |
#include <Magnum/Math/Algorithms/GaussJordan.h> | |
#include <Magnum/Shaders/DistanceFieldVector.h> | |
#include <Magnum/Text/AbstractFont.h> | |
#include <Magnum/Text/DistanceFieldGlyphCache.h> | |
#include <Magnum/Text/Renderer.h> | |
#include "CartesianGrid.h" | |
#include "PlotConfig.h" | |
#include "Units.h" | |
using namespace Magnum; | |
using namespace Magnum::Math::Literals; | |
template <typename Grid, typename F> | |
std::pair<float, float> mathFunctionFindExtrema(const Grid& grid, F f) { | |
float zMin = 1, zMax = -1; | |
grid.spanGridPoints([&](const Vector2& p) { | |
float z = f(p.x(), p.y()); | |
if (zMin > zMax) { | |
zMin = z; | |
zMax = z; | |
} else { | |
if (z < zMin) { | |
zMin = z; | |
} | |
if (z > zMax) { | |
zMax = z; | |
} | |
} | |
}); | |
return {zMin, zMax}; | |
} | |
template <typename F, typename Grid> | |
std::vector<Vector3> mathFunctionPositions(const Grid& grid, F evalF) { | |
std::vector<Vector3> positions{}; | |
const int nPoints = grid.pointsCount(); | |
positions.reserve(nPoints); | |
auto evalPointF = [&](const Vector2 p) { | |
const float z = evalF(p.x(), p.y()); | |
positions.push_back(Vector3{p.x(), p.y(), z}); | |
}; | |
grid.spanGridPoints(evalPointF); | |
return positions; | |
} | |
template <typename F, typename dF, typename Grid> | |
std::pair<std::vector<Vector3>, std::vector<Vector3>> mathFunctionPositionsNormals(const Grid& grid, F evalF, dF evaldF) { | |
std::vector<Vector3> positions{}; | |
std::vector<Vector3> normals{}; | |
const int nPoints = grid.pointsCount(); | |
positions.reserve(nPoints); | |
normals.reserve(nPoints); | |
auto evalPointF = [&](const Vector2 p) { | |
const float z = evalF(p.x(), p.y()); | |
positions.push_back(Vector3{p.x(), p.y(), z}); | |
const Vector2 df = evaldF(p.x(), p.y()); | |
// The normal vector does not need to be normalized. | |
normals.push_back(Vector3{-df.x(), -df.y(), 1}); | |
}; | |
grid.spanGridPoints(evalPointF); | |
return std::make_pair(positions, normals); | |
} | |
template <typename F, typename dF, typename Grid> | |
Trade::MeshData3D mathFunctionMeshData(const Grid& grid, F evalF, dF evaldF) { | |
std::vector<Vector3> positions{}, normals{}; | |
std::tie(positions, normals) = mathFunctionPositionsNormals(grid, evalF, evaldF); | |
std::vector<UnsignedInt> indices{}; | |
indices.reserve(grid.trianglesCount()); | |
auto evalTriangleF = [&](int indexA, int indexB, int indexC) { | |
indices.push_back(indexA); | |
indices.push_back(indexB); | |
indices.push_back(indexC); | |
}; | |
grid.spanTrianglesIndices(evalTriangleF); | |
return Trade::MeshData3D{MeshPrimitive::Triangles, | |
indices, {positions}, {normals}, {}, {}, nullptr}; | |
} | |
template <typename F, typename Grid> | |
Trade::MeshData3D mathFunctionLinesData(const Grid& grid, F evalF) { | |
std::vector<Vector3> positions = mathFunctionPositions(grid, evalF); | |
std::vector<UnsignedInt> indices{}; | |
indices.reserve(grid.linesCount()); | |
auto evalLinesF = [&](int indexA, int indexB) { | |
indices.push_back(indexA); | |
indices.push_back(indexB); | |
}; | |
grid.spanLinesIndices(evalLinesF); | |
return Trade::MeshData3D{MeshPrimitive::Lines, | |
indices, {positions}, {}, {}, {}, nullptr}; | |
} | |
static void addPlaneGridData(UnsignedInt& currentIndex, std::vector<UnsignedInt>& indices, | |
std::vector<Vector3>& positions, const float x0, const float y0, const float x1, const float y1, | |
const Units& unitsX, const Units& unitsY, | |
const Vector3& xVector, const Vector3& yVector, const float z) { | |
const Vector3 zVector = Math::cross(xVector, yVector); | |
positions.push_back(x0 * xVector + y0 * yVector + z * zVector); | |
positions.push_back(x0 * xVector + y1 * yVector + z * zVector); | |
positions.push_back(x1 * xVector + y1 * yVector + z * zVector); | |
positions.push_back(x1 * xVector + y0 * yVector + z * zVector); | |
for (UnsignedInt i : {0, 1, 1, 2, 2, 3, 3, 0}) { | |
indices.push_back(currentIndex + i); | |
} | |
currentIndex += 4; | |
UnitsIterator xIter{unitsX}, yIter{unitsY}; | |
double xval; | |
const char *xlabel; | |
while (xIter.next(xval, xlabel)) { | |
const float xvalf = float(xval); | |
if (xvalf < x0 || xvalf > x1) continue; | |
positions.push_back(xvalf * xVector + y0 * yVector + z * zVector); | |
positions.push_back(xvalf * xVector + y1 * yVector + z * zVector); | |
indices.push_back(currentIndex++); | |
indices.push_back(currentIndex++); | |
} | |
double yval; | |
const char *ylabel; | |
while (yIter.next(yval, ylabel)) { | |
const float yvalf = float(yval); | |
if (yvalf < y0 || yvalf > y1) continue; | |
positions.push_back(x0 * xVector + yvalf * yVector + z * zVector); | |
positions.push_back(x1 * xVector + yvalf * yVector + z * zVector); | |
indices.push_back(currentIndex++); | |
indices.push_back(currentIndex++); | |
} | |
} | |
class MyApp: public Platform::Application { | |
public: | |
explicit MyApp(const Arguments& arguments); | |
Matrix4 transformation() const { | |
return _rotation * _model; | |
} | |
Matrix3 normalsTransformation() const { | |
// For the normals we need the inverse of the transpose of the matrix, excluding the | |
// translation term. | |
// As the rotation is an orthogonal matrix its inverse of transpose corresponds | |
// to the matrix itself. | |
Matrix3 modelInvT = Math::Algorithms::gaussJordanInverted(_model.rotationScaling()).transposed(); | |
return _rotation.rotationScaling() * modelInvT; | |
} | |
private: | |
void drawEvent() override; | |
void mousePressEvent(MouseEvent& event) override; | |
void mouseReleaseEvent(MouseEvent& event) override; | |
void mouseMoveEvent(MouseMoveEvent& event) override; | |
GL::Buffer _indexBuffer, _indexLinesBuffer, _vertexBuffer, _vertexLinesBuffer; | |
GL::Buffer _indexGridBuffer, _vertexGridBuffer; | |
GL::Mesh _mesh, _lines; | |
GL::Mesh _baseGrid; | |
Shaders::Phong _shader; | |
Shaders::Flat3D _flatShader; | |
PluginManager::Manager<Text::AbstractFont> _manager; | |
Containers::Pointer<Text::AbstractFont> _font; | |
Text::DistanceFieldGlyphCache _cache; | |
Containers::Pointer<Text::Renderer2D> _text; | |
Shaders::DistanceFieldVector2D _textShader; | |
Matrix3 _textProjection; | |
Matrix4 _rotation, _model, _projection; | |
Vector2i _previousMousePosition; | |
PlotConfig _plotConfig; | |
}; | |
MyApp::MyApp(const Arguments& arguments): | |
Platform::Application{arguments, Configuration{}.setTitle("Function Plot App"), GLConfiguration{}.setSampleCount(0)}, _cache(Vector2i(2048), Vector2i(512), 22) | |
{ | |
/* Load FreeTypeFont plugin */ | |
_font = _manager.loadAndInstantiate("FreeTypeFont"); | |
if(!_font) std::exit(1); | |
/* Open the font and fill glyph cache */ | |
Utility::Resource rs("fonts"); | |
if(!_font->openSingleData(rs.getRaw("DejaVuSans.ttf"), 110.0f)) { | |
Error() << "Cannot open font file"; | |
std::exit(1); | |
} | |
_font->fillGlyphCache(_cache, "xyzXYZ0123456789.eE+- "); | |
_text.reset(new Text::Renderer2D(*_font, _cache, 0.035f, Text::Alignment::TopLeft)); | |
_text->reserve(40, GL::BufferUsage::DynamicDraw, GL::BufferUsage::StaticDraw); | |
GL::Renderer::enable(GL::Renderer::Feature::DepthTest); | |
GL::Renderer::enable(GL::Renderer::Feature::PolygonOffsetFill); | |
GL::Renderer::setPolygonOffset(1.0f, 1.0f); | |
// Multisampling is enabled by default. | |
// GL::Renderer::enable(GL::Renderer::Feature::Multisampling); | |
GL::Renderer::enable(GL::Renderer::Feature::Blending); | |
GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha, GL::Renderer::BlendFunction::OneMinusSourceAlpha); | |
GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add, GL::Renderer::BlendEquation::Add); | |
auto f = [](float x, float y) { | |
return std::sin(x + y*y); | |
}; | |
auto df = [](float x, float y) { | |
const float c = std::cos(x + y*y); | |
return Vector2{c, 2*y*c}; | |
}; | |
const float plotX1 = -3.0f, plotX2 = 3.0f; | |
const float plotY1 = -2.0f, plotY2 = 2.0f; | |
// To obtain a cartesianGrid use CartesianGrid<NoTransform, 4> | |
const CartesianGrid<NoTransform, 8> grid{Vector2{plotX1, plotY1}, Vector2{plotX2, plotY2}, 80, 80}; | |
const auto limits = mathFunctionFindExtrema(grid, f); | |
Units xUnits{plotX1, plotX2}, yUnits{plotY1, plotY2}; | |
Units zUnits = Units{limits.first, limits.second}; | |
const float zMin = float(zUnits.mark_value(zUnits.begin())); | |
const float zMax = float(zUnits.mark_value(zUnits.end())); | |
const Trade::MeshData3D functionMeshData = mathFunctionMeshData(grid, f, df); | |
const Trade::MeshData3D functionLines = mathFunctionLinesData(grid, f); | |
std::vector<UnsignedInt> gridIndices; | |
std::vector<Vector3> gridPositions; | |
UnsignedInt gridPositionsIndex = 0; | |
addPlaneGridData(gridPositionsIndex, gridIndices, gridPositions, plotX1, plotY1, plotX2, plotY2, xUnits, yUnits, Vector3::xAxis(), Vector3::yAxis(), zMin); | |
addPlaneGridData(gridPositionsIndex, gridIndices, gridPositions, zMin, plotX1, zMax, plotX2, zUnits, xUnits, Vector3::zAxis(), Vector3::xAxis(), plotY2); | |
addPlaneGridData(gridPositionsIndex, gridIndices, gridPositions, plotY1, zMin, plotY2, zMax, yUnits, zUnits, Vector3::yAxis(), Vector3::zAxis(), plotX1); | |
const auto gridData = Trade::MeshData3D{MeshPrimitive::Lines, gridIndices, {gridPositions}, {}, {}, {}, nullptr}; | |
_vertexBuffer.setData(MeshTools::interleave(functionMeshData.positions(0), functionMeshData.normals(0))); | |
Containers::Array<char> indexData; | |
MeshIndexType indexType; | |
UnsignedInt indexStart, indexEnd; | |
std::tie(indexData, indexType, indexStart, indexEnd) = | |
MeshTools::compressIndices(functionMeshData.indices()); | |
_indexBuffer.setData(indexData); | |
_vertexLinesBuffer.setData(functionLines.positions(0)); | |
_indexLinesBuffer.setData(functionLines.indices()); | |
_vertexGridBuffer.setData(gridData.positions(0)); | |
_indexGridBuffer.setData(gridData.indices()); | |
_mesh.setPrimitive(functionMeshData.primitive()) | |
.setCount(functionMeshData.indices().size()) | |
.addVertexBuffer(_vertexBuffer, 0, Shaders::Phong::Position{}, | |
Shaders::Phong::Normal{}) | |
.setIndexBuffer(_indexBuffer, 0, indexType, indexStart, indexEnd); | |
_lines.setPrimitive(functionLines.primitive()) | |
.setCount(functionLines.indices().size()) | |
.addVertexBuffer(_vertexLinesBuffer, 0, Shaders::Flat3D::Position{}) | |
.setIndexBuffer(_indexLinesBuffer, 0, MeshIndexType::UnsignedInt, 0, functionLines.indices().size()); | |
_baseGrid.setPrimitive(gridData.primitive()) | |
.setCount(gridData.indices().size()) | |
.addVertexBuffer(_vertexGridBuffer, 0, Shaders::Flat3D::Position{}) | |
.setIndexBuffer(_indexGridBuffer, 0, MeshIndexType::UnsignedInt, 0, gridData.indices().size()); | |
_model = Matrix4::scaling({2.0f / grid.xSpan(), 2.0f / grid.ySpan(), 0.5f / (zMax - zMin)}) * Matrix4::translation({-grid.xCenter(), -grid.yCenter(), -zMin}); | |
_rotation = Matrix4::rotationX(-60.0_degf); | |
_projection = | |
Matrix4::perspectiveProjection( | |
35.0_degf, Vector2{windowSize()}.aspectRatio(), 0.01f, 100.0f)* | |
Matrix4::translation(Vector3::zAxis(- _plotConfig.cameraDistance)); | |
_textProjection = Matrix3::translation({0.5f, 0.0f}) * Matrix3::scaling(Vector2::yScale(Vector2(GL::defaultFramebuffer.viewport().size()).aspectRatio())); | |
_plotConfig.overwriteGrids = true; | |
// _plotConfig.drawSurface = false; | |
} | |
void MyApp::drawEvent() { | |
GL::defaultFramebuffer.clear(GL::FramebufferClear::Depth).clearColor(Color4{1.0f}); | |
_flatShader.setTransformationProjectionMatrix(_projection*transformation()); | |
if (_plotConfig.drawGrids) { | |
_flatShader.setColor(Color4{0.7f}); | |
_baseGrid.draw(_flatShader); | |
} | |
if (_plotConfig.overwriteGrids) { | |
GL::defaultFramebuffer.clear(GL::FramebufferClear::Depth); | |
} | |
if (_plotConfig.drawSurface) { | |
Color3 color = _plotConfig.surfaceColor; | |
_shader.setLightPosition({7.0f, 5.0f, 2.5f}) | |
.setLightColor(Color3{1.0f}) | |
.setDiffuseColor(color) | |
.setAmbientColor(Color3::fromHsv(color.hue(), 1.0f, 0.4f)) | |
.setTransformationMatrix(transformation()) | |
.setNormalMatrix(normalsTransformation()) | |
.setProjectionMatrix(_projection); | |
_mesh.draw(_shader); | |
} | |
if (_plotConfig.drawSurfaceLines) { | |
_flatShader.setColor(0x555555_rgbf); | |
_lines.draw(_flatShader); | |
} | |
_textShader.bindVectorTexture(_cache.texture()); | |
_textShader.setTransformationProjectionMatrix(_textProjection) | |
.setColor(Color3{0.5f}) | |
.setSmoothness(0.075f); | |
_text->render("Hello world!"); | |
_text->mesh().draw(_textShader); | |
swapBuffers(); | |
} | |
void MyApp::mousePressEvent(MouseEvent& event) { | |
if(event.button() != MouseEvent::Button::Left) return; | |
_previousMousePosition = event.position(); | |
event.setAccepted(); | |
} | |
void MyApp::mouseReleaseEvent(MouseEvent& event) { | |
event.setAccepted(); | |
} | |
void MyApp::mouseMoveEvent(MouseMoveEvent& event) { | |
if(!(event.buttons() & MouseMoveEvent::Button::Left)) return; | |
const Vector2 delta = 3.0f* | |
Vector2{event.position() - _previousMousePosition}/ | |
Vector2{GL::defaultFramebuffer.viewport().size()}; | |
_rotation = | |
Matrix4::rotationX(Rad{delta.y()})* | |
_rotation* | |
Matrix4::rotationY(Rad{delta.x()}); | |
_previousMousePosition = event.position(); | |
event.setAccepted(); | |
redraw(); | |
} | |
MAGNUM_APPLICATION_MAIN(MyApp) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment