From 583b3e62c2b8ac9a843ba329350ed1cabfbe4a65 Mon Sep 17 00:00:00 2001 From: Florent Revest Date: Sun, 21 May 2023 14:39:18 +0200 Subject: [PATCH] Rewrite FlatMesh Problem 1 (Aesthetic): The current FlatMesh works by: - generating an orthonormal grid of base points - leaving a square of free movement to every point This leads to a "stiff" square feeling that clashes with round screens. Solution: The new FlatMesh is based on a circle packing layout instead: each point has a circle in which it can freely move. Base points are not laid out in any obviously predictable patterns leading to a more chaotic layout. In addition, instead of animating quadrilateral shapes, the new FlatMesh animates triangles, this also contributes to a less "square" feeling. --- Problem 2 (CPU use): The current FlatMesh does all computations on the watch CPU leading to unnecessarily wasted CPU cycles and difficulty to evolve this code into more complicated use-cases. Some of the floating point operations like coordinates interpolation are unnecessarily expensive on the floating point unit when the GPU sits next to us and eats these for breakfast. Solution: The new code is essentially split in 4 different levels: - Very expensive operations that only need to be run _once_ (ever). These operations are done in the generate_flatmeshgeometry.py script. The idea is to have complete freedom of visualization/experimentation at development time and not introduce any complexity on the watch. One can implement fairly advanced optimizations leveraging modules like Pandas to parse pre-calculated circle-packing results or like PyVista to generate Delaunay triangulations and triangle strips. The output of this script is cached into packed buffers in a flatmeshgeometry.h header. - Somewhat expensive operations that run very seldomly: These stay on the watch CPU. For example, center/outer colors mixes. Most FlatMeshes never change their colors so we only need to calculate this once. Still, some aspects of this are pre-calculated in Python, like the mix ratio but the actual mixing is done when the colors are available, on the watch. (side note: the current FlatMesh only lets QML update the center and outer colors one after the other leading to two updateColors() run which is unnecessarily expensive especially when animating colors for example in the default applauncher. This implementation exposes a setColors() function that lets animations set both colors at once and avoid running unnecessary updates) - Expensive operations that run very often are off-loaded to the GPU on a vertex shader. Vertex shaders run for every frame and every vertex but they benefit from specialized HW units. For example, interpolating vectors (to mix shifts) is a routine GPU operation and much cheaper than on a CPU - Per-pixel operations are reduced to a bare minimum with the simplest possible fragment shader that just forwards the color of a provoking vertex as the color of each pixel in a triangle. This is called flat-shading and lets us skip the fragment interpolation unit of the GPU. Depending on the GPU implementation, this may or may not save cycles (but this is not actually measured) --- Problem 3 (GPU use): The current FlatMesh geometry makes an inefficient use of the Scene Graph and GPU bus. It creates one QSGGeometryNode per triangle so each triangle has a different VAO and shaders runs are unnecessarily serialized. Also the scene graph is unnecessarily deep, most nodes evolve in the same way anyway and don't need separate handling (they are all marked dirty at the same time for example). Solution: By representing the mesh as one big QSGGeometryNode, one can run more vertex shaders in parallel and save significant GPU cycles. Also, by using an appropriate triangle strip EBO, one can massively reduce the number of vertices transmitted to the GPU and the numbebr of vertex shaders there even needs to run. The flat-shading model maximizes the number of vertices that can be re-used since only the last vertex of each triangle (the provoking vertex) provides the color of the triangle so the two other vertices can be re-used from other triangles, even if they hold the color of another triangle. This leads to significant vertex re-use compared to the current QSGGeometryNodes. Finally, the geometry is never invalidated because we expose shifts in a pre-computed large uniform vectors array and the shader indexes shifts from a hash of the coordinates and a global loop iteration count. This reduces the number of exchanges on the GPU bus since the vertex can pretty much operate independently from the CPU side (only one uniform between 0. and 1. needs to be updated to move the shift mix forward). --- Problem 4 (Maintainability) Contributors in the past have complained that the FlatMesh was a black-box they wouldn't understand. The code was poorly documented and obscure at first read. Solution: One needs a bit of OpenGL background to follow along but this code tries to extensively comment every operation and decision. Hopefully the code is structured in a way that makes it easy to follow for someone willing to learn OpenGL first. The architecture of the code should also make it simple to concentrate on subproblems: for example, if one wants to experiment with replacing the circle packing coordinates with an hexagonal packing, they only need to change the line 16 and 113-114 of generate_flatmeshgeometry.py. The rest of the code will naturally adapt. ----- Some questions are still left up in the air though: On aesthetic: - What should be our round/square screen strategy ? The current FlatMesh renders the same squared content on both screen types and just relies on asteroid-launcher to clip a circle on round screens. This means that the outer colors (in the corners) are never shown on a round watch. This also means that we animate vertices that are off-screen. We have an opportunity to rethink this here. We could imagine having different base points on both screen types. (www.packomania.com also has circles in squares packings although they feel a lot more regular) We could clip a square out of the circle, that's actually what we do here, this is convenient since it means that the outer colors will show on both screen types but this also changes the look and feel a little bit and means that we now have less triangles on a round screen than on a round screen (opposite from the current situation) - How should we tweak the macro-parameters exactly ? I have spent most of my time optimizing the code but not so much time optimizing the look and feel. There are a few parameters that are easy to tweak, namely: the number of points (first line of generate_flatmeshgeometry) which makes things look more or less "low-poly". The color mix ratio exponent (currently 1.7 in generate_flatmeshgeometry.py) which changes how quickly the color gradient changes (it makes the screen overall a little bit brighter by keeping the center color a bit longer) or the shift mix animation easing curve (currently InOutQuad) that changes how the FlatMesh moves. I also had in mind that we could "wrap" the vertices a little bit such that center triangles end up being a bit larger and outer triangles a bit more squished against the screen borders. This could be done with a `pos *= .2*cos(3.14*length(pos))` for example but I did not achieve a satisfying result and left this idea out for now. On CPU use: - The new FlatMesh hooks into the Qt animation framework to interpolate the "shift mix". This has a few pros: 1- the animation is butter smooth since it syncs with the screen refresh rate 2- the animation clock is shared with other animations potentially saving cycles when using multiple animations) 3- this keeps the code very neat and tidy 4- this trivially lets us use different easing curves like the InOutQuad which makes the animation feel more organic then a linear interpolation. However, this also has a drawback: since the update interval is higher than our current manually-tuned timer, updates run more often and this leads to an overall higher CPU usage than the old FlatMesh! With the current FlatMesh, asteroid-flashlight idles at ~13/14% CPU on my bass whereas the new FlatMesh idles at ~16/17%. Either we accept this price and get all the above benefits or we fallback to using a custom timer and save some CPU time but loose the butter-smoothness/shared clock/code simplicity/InOutQuad... On GPU use: - A lot of the GPU optimizations we leverage here depend on the availability of the "flat" GLSL keyword which is only available starting from GLES3.0. This effectively bumps AsteroidOS's minimum requirements. This works on my oldest watch that still runs (my bass, RIP my dory :() so I don't expect it to be an issue but it's good to keep in mind and we should properly test this before rolling it out. If this turns out to be an issue, we could implement a non-optimized version of this that does not benefit from the flat shading and vertex re-use optimizations On maintainability: - It looks like Qt6 changes the SceneGraph API such that we can no longer call OpenGL functions directly (like glEnable()) this could mean that we'll no longer be able to use the fixed index primitive restart extension and instead of using 0xFF in the indices table to jump from one triangles strip to another we may have to generate empty triangles instead (by reusing the last index of the previous strip and the first indeex of the next strip) I expect this shouldn't cost very much on the GPU side since vertex shaders would run just as often and no fragment shaders should run for the empty triangles. - It also looks like they changed the shader format to "Rhi" which means that we may have to do some minor cosmetic changes but overall this should stay very close to the current GLSL. It's not a big deal but definitely an inconvenience on our eventual migration path. --- src/controls/CMakeLists.txt | 2 - src/controls/src/flatmesh.cpp | 247 +++++++- src/controls/src/flatmesh.h | 50 +- src/controls/src/flatmeshgeometry.h | 569 ++++++++++++++++++ src/controls/src/flatmeshnode.cpp | 230 ------- src/controls/src/flatmeshnode.h | 79 --- src/controls/src/generate_flatmeshgeometry.py | 135 +++++ 7 files changed, 969 insertions(+), 343 deletions(-) create mode 100644 src/controls/src/flatmeshgeometry.h delete mode 100644 src/controls/src/flatmeshnode.cpp delete mode 100644 src/controls/src/flatmeshnode.h create mode 100755 src/controls/src/generate_flatmeshgeometry.py diff --git a/src/controls/CMakeLists.txt b/src/controls/CMakeLists.txt index 9dd8a4b..46a430b 100644 --- a/src/controls/CMakeLists.txt +++ b/src/controls/CMakeLists.txt @@ -2,13 +2,11 @@ set(SRC src/controls_plugin.cpp src/application_p.cpp src/flatmesh.cpp - src/flatmeshnode.cpp src/icon.cpp) set(HEADERS src/controls_plugin.h src/application_p.h src/flatmesh.h - src/flatmeshnode.h src/icon.h) add_library(asteroidcontrolsplugin ${SRC} ${HEADERS} resources.qrc) diff --git a/src/controls/src/flatmesh.cpp b/src/controls/src/flatmesh.cpp index 4fc8214..8d36a7f 100644 --- a/src/controls/src/flatmesh.cpp +++ b/src/controls/src/flatmesh.cpp @@ -27,49 +27,220 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include +#include + #include "flatmesh.h" -#include "flatmeshnode.h" +#include "flatmeshgeometry.h" + +// Our Adreno drivers fail to load shaders that are too long so we have to be concise and skip +// every unnecessary character such as spaces, \n, etc... This is effectively one long line! +static const char *vertexShaderSource = + // Qt dynamically injects an "attribute" before main. With GLES3, this should be "in" + "#define attribute in\n" + + // Attributes are per-vertex information, they give base coordinates and colors + "in vec4 coord;" + "in vec4 color;" + + // Uniforms are FlatMesh-wide, they give scaling information, the animation state or shifts + "uniform mat4 matrix;" + "uniform float shiftMix;" + "uniform int loopNb;" + "uniform vec2 shifts[" FLATMESH_SHIFTS_NB_STR "];" + + // This is the color vector outputted here and forwarded to the fragment shaders + // The flat keyword enables flat shading (no interpolation between the vertices of a triangle) + "flat out vec4 fragColor;" + + "void main()" + "{" + // Two vertices can have the same coordinate (if they give different colors to 2 triangles) + // However, they need to move in sync, so we hash their coordinates as an index for shifts + "int shiftIndex = loopNb+floatBitsToInt(coord.x)+floatBitsToInt(coord.y);" + + // Interpolate between (coord + shiftA) and (coord + shiftB) in the [-0.5, 0.5] domain + "vec2 pos = coord.xy + mix(shifts[(shiftIndex)%" FLATMESH_SHIFTS_NB_STR "]," + "shifts[(shiftIndex+1)%" FLATMESH_SHIFTS_NB_STR "]," + "shiftMix);" -FlatMesh::FlatMesh(QQuickItem *parent) : QQuickItem(parent) + // Apply scene graph transformations (FlatMesh position and size) to get the final coords + "gl_Position = matrix * vec4(pos, 0, 1);" + + // Forward the color in the vertex attribute to the fragment shaders + "fragColor = color;" + "}"; + +static const char *fragmentShaderSource = + "#ifdef GL_ES\n" + "precision mediump float;" + "\n#endif\n" + + // The flat keyword disables interpolation in triangles + // Each pixel gets the color of the last vertex of the triangle it belongs to + "flat in vec4 fragColor;" + "out vec4 color;" + + // Just keep the provided color + "void main()" + "{" + "color = fragColor;" + "}"; + +static QByteArray versionedShaderCode(const char *src) { - m_timer.setInterval(90); - m_timer.setSingleShot(false); - connect(&m_timer, SIGNAL(timeout()), this, SLOT(update())); - m_timer.start(); + return (QOpenGLContext::currentContext()->isOpenGLES() + ? QByteArrayLiteral("#version 300 es\n") + : QByteArrayLiteral("#version 330\n")) + + src; +} - m_centerColor = QColor("#ffaa39"); - m_outerColor = QColor("#df4829"); +// This class wraps the FlatMesh vertex and fragment shaders +class SGFlatMeshMaterialShader : public QSGMaterialShader +{ +public: + SGFlatMeshMaterialShader() {} + const char *vertexShader() const override { + return versionedShaderCode(vertexShaderSource); + } + const char *fragmentShader() const override { + return versionedShaderCode(fragmentShaderSource); + } + void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override { + // On every run, update the animation state uniforms + SGFlatMeshMaterial *material = static_cast(newEffect); + program()->setUniformValue(m_shiftMix_id, material->shiftMix()); + program()->setUniformValue(m_loopNb_id, material->loopNb()); + + if (state.isMatrixDirty()) { + // Vertices coordinates are always in the [-0.5, 0.5] range, modify QtQuick's projection matrix to do the scaling for us + QMatrix4x4 combinedMatrix = state.combinedMatrix(); + combinedMatrix.scale(material->width(), material->height()); + combinedMatrix.translate(0.5, 0.5); + combinedMatrix.scale(material->screenScaleFactor()); + program()->setUniformValue(m_matrix_id, combinedMatrix); + } + // Enable a mode such that 0xFF indices mean "restart a strip" + m_glFuncs->glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); + } + char const *const *attributeNames() const override { + // Map attribute numbers to attribute names in the vertex shader + static const char *const attr[] = { "coord", "color", nullptr }; + return attr; + } + void deactivate() override { + m_glFuncs->glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX); + } +private: + void initialize() override { + // Seed the array of shifts with pre-randomized shifts + program()->setUniformValueArray("shifts", flatmesh_shifts, flatmesh_shifts_nb, 2); + // Get the ids of the uniforms we regularly update + m_matrix_id = program()->uniformLocation("matrix"); + m_shiftMix_id = program()->uniformLocation("shiftMix"); + m_loopNb_id = program()->uniformLocation("loopNb"); + // Retrieve OpenGL functions available on all platforms + m_glFuncs = QOpenGLContext::currentContext()->functions(); + } + int m_matrix_id; + int m_shiftMix_id; + int m_loopNb_id; + QOpenGLFunctions *m_glFuncs; +}; +QSGMaterialShader *SGFlatMeshMaterial::createShader() const +{ + return new SGFlatMeshMaterialShader; +} + +FlatMesh::FlatMesh(QQuickItem *parent) : QQuickItem(parent), m_geometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), flatmesh_vertices_sz, flatmesh_indices_sz) +{ + // Dilate the FlatMesh more or less on squared or round screens + QSettings machineConf("/etc/asteroid/machine.conf", QSettings::IniFormat); + m_material.setScreenScaleFactor(machineConf.value("Display/ROUND", false).toBool() ? 1.2 : 1.7); + + // Iterate over all vertices and assign them the coordinates of their base point from flatmesh_vertices + QSGGeometry::ColoredPoint2D *vertices = m_geometry.vertexDataAsColoredPoint2D(); + for (int i = 0; i < flatmesh_vertices_sz; i++) { + vertices[i].x = flatmesh_vertices[i].x(); + vertices[i].y = flatmesh_vertices[i].y(); + } + // Copy the indices buffer (already in the right format) + memcpy(m_geometry.indexData(), flatmesh_indices, sizeof(flatmesh_indices)); + + // Give initial colors to the vertices + setColors(QColor("#ffaa39"), QColor("#df4829")); + + + // m_animation interpolates the shiftMix, a float between 0.0 and 1.0 + // This is used by the vertex shader as the mix ratio between two shifts + m_animation.setStartValue(0.0); + m_animation.setEndValue(1.0); + m_animation.setDuration(4000); + m_animation.setLoopCount(-1); + m_animation.setEasingCurve(QEasingCurve::InOutQuad); + QObject::connect(&m_animation, &QVariantAnimation::currentLoopChanged, [this]() { + m_material.incrementLoopNb(); + }); + QObject::connect(&m_animation, &QVariantAnimation::valueChanged, [this](const QVariant& value) { + m_material.setShiftMix(value.toFloat()); + update(); + }); + + // Run m_animation depending on the item's visibility connect(this, SIGNAL(visibleChanged()), this, SLOT(maybeEnableAnimation())); + setAnimated(true); + // Tell QtQuick we have graphic content and that updatePaintNode() needs to run setFlag(ItemHasContents); - setAnimated(true); } -void FlatMesh::setCenterColor(QColor c) +void FlatMesh::updateColors() { - if (c == m_centerColor) + // Iterate over all vertices and give them the rgb values of the triangle they represent + // In the flat shading model we use, each triangle is colored by its last vertex + QSGGeometry::ColoredPoint2D *vertices = m_geometry.vertexDataAsColoredPoint2D(); + for (int i = 0; i < flatmesh_vertices_sz; i++) { + // Ratios are pre-calculated to save some computation, we just need to do the mix + // We do the color blending on the CPU because center and outer colors change rarely + // and it would be a waste of GPU time to re-calculate that in every vertex shader + float ratio = flatmesh_vertices[i].z(); + float inverse_ratio = 1-ratio; + vertices[i].r = m_centerColor.red()*inverse_ratio + m_outerColor.red()*ratio; + vertices[i].g = m_centerColor.green()*inverse_ratio + m_outerColor.green()*ratio; + vertices[i].b = m_centerColor.blue()*inverse_ratio + m_outerColor.blue()*ratio; + } + m_geometryDirty = true; +} + +void FlatMesh::setColors(QColor center, QColor outer) +{ + if (center == m_centerColor && outer == m_outerColor) return; - m_centerColor = c; + m_centerColor = center; + m_outerColor = outer; + updateColors(); update(); } +void FlatMesh::setCenterColor(QColor c) +{ + setColors(c, m_outerColor); +} + void FlatMesh::setOuterColor(QColor c) { - if (c == m_outerColor) - return; - m_outerColor = c; - update(); + setColors(m_centerColor, c); } void FlatMesh::maybeEnableAnimation() { - if (isVisible() && m_animated) { - m_timer.start(); - } else { - m_timer.stop(); - } - update(); + // Only run the animation if the item is visible. No point running the shaders if this is hidden + if (isVisible() && m_animated) + m_animation.start(); + else + m_animation.pause(); } void FlatMesh::setAnimated(bool animated) @@ -81,16 +252,32 @@ void FlatMesh::setAnimated(bool animated) maybeEnableAnimation(); } +void FlatMesh::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + // On resizes, tell the vertex shader about the new size so the transformation matrix compensates it + m_material.setSize(newGeometry.width(), newGeometry.height()); + + QQuickItem::geometryChanged(newGeometry, oldGeometry); +} + +// Called by the SceneGraph on every update() QSGNode *FlatMesh::updatePaintNode(QSGNode *old, UpdatePaintNodeData *) { - FlatMeshNode *n = static_cast(old); - if (!n) - n = new FlatMeshNode(window(), boundingRect()); - - n->setAnimated(m_animated); - n->setRect(boundingRect()); - n->setCenterColor(m_centerColor); - n->setOuterColor(m_outerColor); + // On the first update(), create a scene graph node for the mesh + QSGGeometryNode *n = static_cast(old); + if (!n) { + n = new QSGGeometryNode; + n->setOpaqueMaterial(&m_material); + n->setGeometry(&m_geometry); + } + + // On every update(), mark the material dirty so the shaders run again + n->markDirty(QSGNode::DirtyMaterial); + // And if colors changed, mark the geometry dirty so the new vertex attributes are sent to the GPU + if (m_geometryDirty) { + n->markDirty(QSGNode::DirtyGeometry); + m_geometryDirty = false; + } return n; } diff --git a/src/controls/src/flatmesh.h b/src/controls/src/flatmesh.h index 3014ac2..fdccae4 100644 --- a/src/controls/src/flatmesh.h +++ b/src/controls/src/flatmesh.h @@ -33,8 +33,39 @@ #include #include #include -#include +#include +#include +// This is the scene graph material used by FlatMesh. It just creates the Shader object and holds values for some of the uniforms +class SGFlatMeshMaterial : public QSGMaterial +{ +public: + // Start the animation at a random point. Disable SceneGraph optimizations that assume our vertex coordinates to be in pixels + SGFlatMeshMaterial() : m_loopNb(random()) { setFlag(QSGMaterial::RequiresFullMatrix); } + int compare(const QSGMaterial *other) const override { return 0; } + void setScreenScaleFactor(float screenScaleFactor) { m_screenScaleFactor = screenScaleFactor; } + float screenScaleFactor() { return m_screenScaleFactor; } + void setShiftMix(float shiftMix) { m_shiftMix = shiftMix; } + float shiftMix() { return m_shiftMix; } + void setSize(float width, float height) { m_width = width; m_height = height; } + float width() { return m_width; } + float height() { return m_height; } + void incrementLoopNb() { m_loopNb++; } + int loopNb() { return m_loopNb; } +protected: + QSGMaterialType *type() const override { static QSGMaterialType type; return &type; } + QSGMaterialShader *createShader() const override; +private: + float m_screenScaleFactor; + float m_shiftMix; + float m_width; + float m_height; + int m_loopNb; +}; + +struct FlatMeshVertex; + +// The QtQuick item per-se, this is the highest level construct that exposes properties to QML class FlatMesh : public QQuickItem { Q_OBJECT @@ -48,6 +79,10 @@ class FlatMesh : public QQuickItem bool getAnimated() const { return m_animated; } void setAnimated(bool animated); + // As an optimization for color animations, make it possible to change the two colors + // on one call. Then, updateColors() will only run once saving some CPU cycles + Q_INVOKABLE void setColors(QColor center, QColor outer); + QColor getCenterColor() const { return m_centerColor; } void setCenterColor(QColor c); @@ -59,14 +94,25 @@ class FlatMesh : public QQuickItem protected: QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data); + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; private slots: void maybeEnableAnimation(); + void updateColors(); private: QColor m_centerColor, m_outerColor; bool m_animated; - QTimer m_timer; + QVariantAnimation m_animation; + // Note: the Qt documentation says "It is crucial that [...] interaction with the scene + // graph happens exclusively on the render thread, primarily during the updatePaintNode() + // call. The rule of thumb is to only use classes with the "QSG" prefix inside the + // QQuickItem::updatePaintNode() function." + // However, no hell broke loose for instantiating these in the FlatMesh constructor so... + // It doesn't look like we're doing anything nasty here but let's keep an eye on it. + SGFlatMeshMaterial m_material; + QSGGeometry m_geometry; + bool m_geometryDirty; }; #endif // FLATMESH_H diff --git a/src/controls/src/flatmeshgeometry.h b/src/controls/src/flatmeshgeometry.h new file mode 100644 index 0000000..2423685 --- /dev/null +++ b/src/controls/src/flatmeshgeometry.h @@ -0,0 +1,569 @@ +// Do not modify manually! This file is generated by generate_flatmeshgeometry.py + +static const QVector3D flatmesh_vertices[] = { + QVector3D(-0.07590353228766045, -0.448504906868135, 1.0), + QVector3D(-0.062224940488613, -0.35930409318402756, 0.9061733334183945), + QVector3D(-0.1629414816089452, -0.424697623338794, 1.0), + QVector3D(-0.15027899526611474, -0.3353552884089378, 0.9075174446281145), + QVector3D(-0.24356755120684306, -0.38417814318053056, 1.0), + QVector3D(-0.23090506486401255, -0.2948358082506744, 0.921521610515689), + QVector3D(-0.31460903971810106, -0.3285409408676378, 1.0), + QVector3D(-0.25951141051372995, -0.2078667893854162, 0.8770518376594514), + QVector3D(-0.3732704068345872, -0.25997538552732274, 0.983261036334784), + QVector3D(-0.3434807221115272, -0.1747992970094667, 0.8920861036693435), + QVector3D(-0.4298739640827997, -0.14874936202309635, 1.0), + QVector3D(-0.3641174376320337, -0.0869555872538635, 0.945253439027785), + QVector3D(-0.4508623386256416, -0.06032120150344015, 1.0), + QVector3D(-0.3845775789108305, 0.00092941709814545, 0.9441538209381701), + QVector3D(-0.4505726347395617, 0.0624683801805993, 1.0), + QVector3D(-0.31914905303085206, 0.0630703808199446, 0.8822652687763839), + QVector3D(-0.42937666241189487, 0.1501788249872798, 0.9734756956158577), + QVector3D(-0.3391457840919671, 0.15106198336229185, 0.8640739101149884), + QVector3D(-0.3912843694651375, 0.2319796109808104, 1.0), + QVector3D(-0.27332862605861374, 0.2127911748320783, 0.890127295771891), + QVector3D(-0.32144975135481846, 0.28912431751157197, 0.9718710782503599), + QVector3D(-0.2238771660764429, 0.288269280775012, 0.8522461888204537), + QVector3D(-0.2719982913726476, 0.36460242345450566, 0.9961237618777129), + QVector3D(-0.1946769303112335, 0.41111907095528116, 1.0), + QVector3D(0.09820876379176866, -0.42192652852178536, 1.0), + QVector3D(0.0278142706527945, -0.3654729484155787, 0.8428009938830104), + QVector3D(0.0141212827157268, -0.454663157813563, 0.9999238494308663), + QVector3D(0.18810955777419616, -0.4141651747461811, 1.0), + QVector3D(0.10520039266732495, -0.31906411853749217, 0.9591445378112252), + QVector3D(-0.00118433127347995, -0.28002429449158506, 0.6538012733845537), + QVector3D(-0.062224940488613, -0.35930409318402756, 0.6906408936098238), + QVector3D(-0.0905735272994765, -0.26769696692528216, 0.5938746244033252), + QVector3D(-0.15027899526611474, -0.3353552884089378, 0.6947577500788529), + QVector3D(-0.1711995968973743, -0.22717748676701874, 0.601418176088109), + QVector3D(-0.23090506486401255, -0.2948358082506744, 0.7076285541603962), + QVector3D(-0.25951141051372995, -0.2078667893854162, 0.6670897418730211), + QVector3D(0.2633504907004984, -0.2649712148677251, 1.0), + QVector3D(0.2227806334425657, -0.1843464495257893, 0.7126913588122905), + QVector3D(0.1547060506695305, -0.2436215496730266, 0.6202710385866826), + QVector3D(0.1336269904007723, -0.15588293451819854, 0.444613885490116), + QVector3D(0.06801810442391025, -0.21783916177917895, 0.3848657789315288), + QVector3D(0.0472593634161869, -0.12974819939131274, 0.2566417303154027), + QVector3D(-0.0185577946171664, -0.1914773908610992, 0.2458889308205983), + QVector3D(-0.0391082635683985, -0.1036134642644269, 0.15975554893883206), + QVector3D(-0.10506055127802755, -0.16579052654982404, 0.20263932082321262), + QVector3D(-0.1254758905529839, -0.0774787291375411, 0.1690617375016782), + QVector3D(-0.19129304858633725, -0.1392079206073276, 0.2627102108614229), + QVector3D(-0.21184351753756936, -0.0513439940106553, 0.2812221434118502), + QVector3D(-0.2776606755709227, -0.11307318548044175, 0.4152880511856543), + QVector3D(-0.29821114452215475, -0.0252092588837695, 0.4784939259424734), + QVector3D(-0.3641174376320337, -0.0869555872538635, 0.6459086579808486), + QVector3D(-0.3845775789108305, 0.00092941709814545, 0.7468618883321203), + QVector3D(0.19528840283181184, -0.3242159915662338, 0.934981911653232), + QVector3D(0.10520039266732495, -0.31906411853749217, 0.6807003449785606), + QVector3D(0.06801810442391025, -0.21783916177917895, 0.5156052571365449), + QVector3D(-0.00118433127347995, -0.28002429449158506, 0.5034809606882725), + QVector3D(-0.0185577946171664, -0.1914773908610992, 0.36508742191134325), + QVector3D(-0.0905735272994765, -0.26769696692528216, 0.41713662399322), + QVector3D(-0.10506055127802755, -0.16579052654982404, 0.3382245576678877), + QVector3D(-0.1711995968973743, -0.22717748676701874, 0.42503321058944654), + QVector3D(-0.19129304858633725, -0.1392079206073276, 0.3808173279430114), + QVector3D(-0.25951141051372995, -0.2078667893854162, 0.5155447906229977), + QVector3D(-0.2776606755709227, -0.11307318548044175, 0.5312439809450881), + QVector3D(-0.3434807221115272, -0.1747992970094667, 0.6966838863053082), + QVector3D(-0.3641174376320337, -0.0869555872538635, 0.7484911886249883), + QVector3D(0.2781653886287355, -0.35991720523172854, 0.0), + QVector3D(0.10520039266732495, -0.31906411853749217, 0.8870003285059984), + QVector3D(0.1547060506695305, -0.2436215496730266, 0.7246815811358405), + QVector3D(0.34982112161129786, -0.29076310043255477, 0.0), + QVector3D(0.3138619937615597, -0.19019836264438564, 0.9196157275606832), + QVector3D(0.289204641387069, -0.1033974044412577, 0.6318611680464125), + QVector3D(0.19944414843412564, -0.09415374304841205, 0.4757192778533415), + QVector3D(0.26865417243583684, -0.01553347784458545, 0.45513270392578115), + QVector3D(0.17889367948289356, -0.0062898164517398, 0.3353175670430853), + QVector3D(0.28165886490790315, 0.073759687318309, 0.40176025302696283), + QVector3D(0.1583432105316615, 0.08157411014493245, 0.3175513491619195), + QVector3D(0.22416036856501484, 0.1433033016147189, 0.3991108048083323), + QVector3D(0.1377927415804294, 0.1694380367416047, 0.33145020078782655), + QVector3D(0.20360989961378276, 0.23116722821139116, 0.45312245781512656), + QVector3D(0.11724227262919736, 0.25730196333827693, 0.470268003937791), + QVector3D(0.1830594306625507, 0.31903115480806343, 0.6292240441081979), + QVector3D(0.10300355013507664, 0.3606658442397353, 0.7078676992691899), + QVector3D(0.16882070816842995, 0.42239503570952175, 0.9227723446941978), + QVector3D(0.07860246081920055, 0.44803381926336405, 1.0), + QVector3D(0.4003326246723591, -0.21599024820921525, 0.0), + QVector3D(0.3138619937615597, -0.19019836264438564, 1.0), + QVector3D(0.379433737327603, -0.1282085423767802, 0.9558178208636355), + QVector3D(0.289204641387069, -0.1033974044412577, 0.7667701732247899), + QVector3D(0.35513000763583585, -0.04130790892586405, 0.7549859922750332), + QVector3D(0.26865417243583684, -0.01553347784458545, 0.6015114129877066), + QVector3D(0.3689915156374611, 0.04785626530920545, 0.6759742405964898), + QVector3D(0.28165886490790315, 0.073759687318309, 0.5998146685163432), + QVector3D(0.3474760229412565, 0.13548887878809546, 0.7204165109408786), + QVector3D(0.22416036856501484, 0.1433033016147189, 0.5974510573294286), + QVector3D(0.2899775265983682, 0.20503249308450536, 0.6705962236750833), + QVector3D(0.20360989961378276, 0.23116722821139116, 0.5966049870187571), + QVector3D(0.2694270576471361, 0.29289641968117763, 0.749945880296471), + QVector3D(0.1830594306625507, 0.31903115480806343, 0.765655743824077), + QVector3D(0.24887658869590404, 0.3807603462778499, 0.9534792247573457), + QVector3D(0.16882070816842995, 0.42239503570952175, 1.0), + QVector3D(0.44929248325579463, -0.07109333434563204, 0.0), + QVector3D(0.379433737327603, -0.1282085423767802, 1.0), + QVector3D(0.35513000763583585, -0.04130790892586405, 0.9436335066625672), + QVector3D(0.1336269904007723, -0.15588293451819854, 0.37813399589459884), + QVector3D(0.1130765214495402, -0.06801900792152625, 0.24607124096483984), + QVector3D(0.0472593634161869, -0.12974819939131274, 0.18269910245138068), + QVector3D(0.0267088944649548, -0.04188427279464045, 0.09039582674458865), + QVector3D(-0.0391082635683985, -0.1036134642644269, 0.07738429550857409), + QVector3D(-0.05965873251963055, -0.01574953766775465, 0.03590890696909567), + QVector3D(-0.1254758905529839, -0.0774787291375411, 0.08757200187501528), + QVector3D(-0.146026359504216, 0.0103851974591311, 0.11008467629063202), + QVector3D(-0.21184351753756936, -0.0513439940106553, 0.2089375138571442), + QVector3D(-0.2323939864888014, 0.0365199325860169, 0.2792880714227354), + QVector3D(-0.29821114452215475, -0.0252092588837695, 0.4134829612273475), + QVector3D(-0.31914905303085206, 0.0630703808199446, 0.5222389146126992), + QVector3D(-0.3845775789108305, 0.00092941709814545, 0.687440567002935), + QVector3D(0.1130765214495402, -0.06801900792152625, 0.22476632697223556), + QVector3D(0.09252605249830816, 0.01984491867514595, 0.13705760579493692), + QVector3D(0.0267088944649548, -0.04188427279464045, 0.06445501863002251), + QVector3D(0.0061584255137227, 0.04597965380203175, 0.020679319541762695), + QVector3D(-0.05965873251963055, -0.01574953766775465, 0.0016858497164255269), + QVector3D(-0.08020920147086265, 0.0721143889289176, 0.0331098589472019), + QVector3D(-0.146026359504216, 0.0103851974591311, 0.08519146889074841), + QVector3D(-0.16657682845544805, 0.0982491240558034, 0.16453655999424033), + QVector3D(-0.2323939864888014, 0.0365199325860169, 0.25847731788074946), + QVector3D(-0.2529444554400335, 0.1243838591826892, 0.3745748888718783), + QVector3D(-0.31914905303085206, 0.0630703808199446, 0.5037254221675496), + QVector3D(-0.3391457840919671, 0.15106198336229185, 0.6521546465256765), + QVector3D(0.09252605249830816, 0.01984491867514595, 0.16959003978167916), + QVector3D(0.07197558354707605, 0.10770884527181825, 0.13486019776839087), + QVector3D(0.0061584255137227, 0.04597965380203175, 0.061939499577595765), + QVector3D(-0.0143920434375093, 0.13384358039870406, 0.0856504882619879), + QVector3D(-0.08020920147086265, 0.0721143889289176, 0.0725022739347035), + QVector3D(-0.10075967042209474, 0.15997831552558986, 0.15332523167640522), + QVector3D(-0.16657682845544805, 0.0982491240558034, 0.19614767338593223), + QVector3D(-0.18712729740668016, 0.18611305065247566, 0.31670508565750866), + QVector3D(-0.2529444554400335, 0.1243838591826892, 0.40217938300821915), + QVector3D(-0.27332862605861374, 0.2127911748320783, 0.5557023969502217), + QVector3D(-0.3391457840919671, 0.15106198336229185, 0.6769306301522307), + QVector3D(0.454485742634088, 0.01899229951532675, 0.0), + QVector3D(0.35513000763583585, -0.04130790892586405, 1.0), + QVector3D(0.3689915156374611, 0.04785626530920545, 0.9052192549883082), + QVector3D(0.441103930715987, 0.11109418371607285, 0.0), + QVector3D(0.3689915156374611, 0.04785626530920545, 1.0), + QVector3D(0.3474760229412565, 0.13548887878809546, 0.9256077890388497), + QVector3D(0.40685859591622436, 0.2034307760089825, 0.0), + QVector3D(0.3474760229412565, 0.13548887878809546, 1.0), + QVector3D(0.2899775265983682, 0.20503249308450536, 0.9033943322033756), + QVector3D(0.051425114595844, 0.1955727718684905, 0.24012151698030998), + QVector3D(-0.0349425123887414, 0.2217075069953763, 0.3752820185834765), + QVector3D(-0.0143920434375093, 0.13384358039870406, 0.2485317921201491), + QVector3D(-0.10075967042209474, 0.15997831552558986, 0.237749406352912), + QVector3D(0.07197558354707605, 0.10770884527181825, 0.2207382011657191), + QVector3D(-0.0143920434375093, 0.13384358039870406, 0.17642262275065648), + QVector3D(0.051425114595844, 0.1955727718684905, 0.36747081014643934), + QVector3D(0.0308746456446119, 0.2834366984651628, 0.43193606682410396), + QVector3D(-0.0584485632483233, 0.312621163775509, 0.4885094067935636), + QVector3D(-0.1213101393733268, 0.2478422421222621, 0.47938243740554104), + QVector3D(-0.1463965464394275, 0.33452017213685614, 0.6300440169843945), + QVector3D(-0.2238771660764429, 0.288269280775012, 0.6841090180154307), + QVector3D(-0.1946769303112335, 0.41111907095528116, 0.9043159895450578), + QVector3D(-0.18712729740668016, 0.18611305065247566, 0.3926425398632497), + QVector3D(-0.2238771660764429, 0.288269280775012, 0.5690683361556731), + QVector3D(-0.27332862605861374, 0.2127911748320783, 0.6495543504067224), + QVector3D(0.35869783390037463, 0.2797389169452788, 1.0), + QVector3D(0.2899775265983682, 0.20503249308450536, 1.0), + QVector3D(0.2694270576471361, 0.29289641968117763, 0.9371831867970333), + QVector3D(-0.0349425123887414, 0.2217075069953763, 0.35521142631746444), + QVector3D(0.0308746456446119, 0.2834366984651628, 0.6112365490605847), + QVector3D(0.01348041341455295, 0.3719795245790143, 0.715674578859033), + QVector3D(-0.0584485632483233, 0.312621163775509, 0.6476611352091383), + QVector3D(-0.1045028917807815, 0.4144408394916967, 0.8160983620028505), + QVector3D(-0.1463965464394275, 0.33452017213685614, 0.8118217511280866), + QVector3D(-0.1946769303112335, 0.41111907095528116, 0.9901036442633289), + QVector3D(-0.32144975135481846, 0.28912431751157197, 1.0), + QVector3D(0.01348041341455295, 0.3719795245790143, 0.9285825980226923), + QVector3D(-0.0235351305104951, 0.4542731505717594, 1.0), + QVector3D(-0.1045028917807815, 0.4144408394916967, 0.9945268034598578), +}; +static const int flatmesh_vertices_sz = 178; + +static const unsigned short flatmesh_indices[] = { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 65535, + 24, + 25, + 26, + 1, + 0, + 65535, + 26, + 27, + 24, + 28, + 25, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 65535, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 65535, + 52, + 38, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65535, + 65, + 52, + 27, + 66, + 65535, + 65, + 36, + 52, + 67, + 65535, + 65, + 68, + 36, + 69, + 37, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 65535, + 68, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 65535, + 84, + 100, + 101, + 102, + 65535, + 37, + 71, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 65535, + 71, + 73, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 65535, + 73, + 75, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 65535, + 100, + 139, + 140, + 141, + 65535, + 139, + 142, + 143, + 144, + 65535, + 142, + 145, + 146, + 147, + 65535, + 148, + 149, + 150, + 151, + 65535, + 75, + 77, + 152, + 148, + 153, + 65535, + 77, + 79, + 154, + 155, + 149, + 156, + 157, + 158, + 159, + 160, + 65535, + 133, + 157, + 161, + 162, + 163, + 65535, + 145, + 164, + 165, + 166, + 65535, + 98, + 96, + 164, + 65535, + 157, + 133, + 167, + 65535, + 79, + 81, + 168, + 169, + 170, + 171, + 172, + 173, + 65535, + 22, + 18, + 174, + 65535, + 81, + 83, + 175, + 176, + 177, + 23, +}; +static const int flatmesh_indices_sz = 249; + +static const float flatmesh_shifts[] = { + -0.014455721149386655, -0.021944026100648904, + -0.0005417119668014425, -0.039969767519674536, + -0.005524899294510257, -0.010139476531081666, + 0.012758185235742632, -0.039804143287190986, + 0.016032993379478742, 0.021338958241106924, + -0.0009254513955395854, 0.028055492160101097, + 0.0004533243420474752, 0.03674686013983172, + -0.022697070664360105, -0.030363814350102606, + 0.007743521230965211, -0.019560700344122616, + -0.04334631296523202, -0.009383657661179951, + 0.03256188046993191, 0.025330835453787875, + -0.021714757037414396, 0.025152464057466416, + 0.012338141279632982, -0.02172516565176803, + 0.02508775160189217, 0.014871469887621143, + 0.010852184611772639, -0.03526544934319527, + 0.011078774776345005, 0.041682558124455774, + 0.0035375768838676394, -0.0060719508956933056, + 0.02108125222425731, -0.0037205194306797465, + -0.02161983262917156, 0.011125161684785273, + 0.021947781859247333, 0.02214987214505951, + 0.004894857911667648, 0.018081294039064028, + 0.02099854515886201, 0.017851099718312842, + -0.01982622499699757, -0.013183682802725872, + -0.036680121754048334, -0.002642264405302571, + 0.02521447679777857, 0.01953508030294833, + -0.01693167887076652, 0.014069252976717626, + -0.025844106914715094, -0.022012426782007103, + 0.04210257853677726, 0.002367042994043245, + 0.020791646218799684, -0.02627824459386542, + 0.012239808414337286, -0.0309652953716663, + 0.03599030216478599, -0.010410258709267803, + -0.023578006946172538, -0.026465287624940905, + -0.011309011546572234, -0.0407531115180364, + 0.015022118359744516, 0.04137053265491031, + 0.038489689982268656, -0.02243533716317161, + 0.02478205349145701, 0.019303614092614562, + -0.019693277099741027, -0.02693662442571409, + 0.029974181863058575, -0.0030079124966440464, + 0.004627833934675862, 0.015622344559532944, + 0.00917924949277589, 0.0026284743830412346, + 0.03253839553084739, -0.025151718686619636, + 0.0032923995101349257, 0.015154019103672324, + -0.018772053876719638, 0.026103606893666736, + 0.012255501543728734, 0.019321871743076518, + 0.008202266536389611, 0.020395010572526788, + 0.028667046535234678, 0.011693898409761561, + -0.030878915940646755, 0.008102717451669298, + -0.03811272641333141, 0.007723695569871546, + 0.024820437019682947, -0.012101951313072762, + 0.03155875764449122, 0.013031815719337321, + 0.039680689480552395, 0.010880074383769719, + 0.009130663377037737, 0.012704706856865741, + 0.01777365169239631, -0.026130854618725316, + -0.014087339817733453, 0.026895218972944657, + -0.03907189506332969, -0.00934785615327756, + -0.025841865289036075, -0.001990416616211092, + 0.005740441202764657, 0.03916075306560983, + 0.016692106164581704, -0.032664932436886056, + 0.012458530546389875, -0.01664408530008782, + 0.005350175460556124, 0.02147075445879694, + -0.012728847696550414, -0.040885766630212886, + 0.020727563524314336, -0.027189559627245403, + -0.03937889016407205, 0.00685132135573799, + -0.012459352627072663, 0.006470183072552711, + -0.007923360288363972, -0.03851489683366685, + -0.008493606579398153, -0.041275856024777145, + 0.014302511133420025, -0.02394499753493127, + 0.0009595811232725851, -0.02284333891494767, + -0.005786780813590626, 0.019446888135934474, + 0.021347205698869935, 0.009737932605521532, + -0.006320426192678588, 0.04398189335415962, + -0.011336790581631088, -0.042823619127327954, + -0.01442988854433544, 0.03731838302401159, + -0.027437202794864314, -0.02951238433261903, + 0.02940054296296972, -0.016659949044084646, + -0.018428722166114863, -0.025969164336010115, + 0.024709086286447933, 0.0013642540454152073, + -0.028170913712792317, 0.015685931001087235, + 0.00924314522353013, -0.0388742267123793, + 0.00696205950412131, 0.009621275477510339, + 0.017751295946310926, -0.033092060190639136, + -0.008261284211193763, 0.0007092181693598742, + 0.030416399135691104, 0.027551072447996004, + -0.02230527462634699, 0.016383793194602304, + 0.024600726041301275, 0.02596089182561636, + 0.011357288208597079, 0.03699123476232083, + 0.023713456790261454, 0.00113234713125188, + 0.002502185041645086, -0.02925074933009578, + -0.015337283721453971, -0.006416999939164213, + -0.03169783297426491, -0.0283394201225888, + -0.000708085318734062, -0.0018007775896010474, + 0.02950008563117916, 0.014583676624964534, + -0.0020427663974829863, 0.013199382988909799, + 0.03507639270124268, -0.025085907145944213, + -0.03122915994870905, -0.024291642921123995, + -0.005658772650946406, 0.036729372190078095, + 0.04111081065596769, 0.00225620345956127, + 0.01800701121624554, 0.03058462742023514, + -0.014742280204694801, 0.0002762920246333182, + 0.002248234532322464, 0.00861076332609133, + 0.013647490362448387, -0.006681817271052268, + -0.012296102258394247, 0.010122797006050814, + 0.020928081198451364, -0.01777445717405529, + -0.032638344817320995, 0.030339197307702227, + 0.01109615616036932, 0.042009648989523224, + -0.03373940115536797, -0.01949275900086155, + 0.018995093333682645, 0.007878765969187485, + -0.006154470295669073, 0.018875348073310257, + -0.021650550509754424, -0.020311562994237937, + -0.03585048010374008, 0.02422316055621331, + -0.028050145958904642, 0.01419119660414566, + -0.011261954203254267, -0.035069408873473734, + -0.02300749248155945, 0.03586811459781664, + 0.023144047662494484, 0.036825186135317434, + 0.016402061363406183, -0.016109727124898734, + 0.030311403022036724, -0.010363524515611294, + 0.0294962061451393, 0.027120069030887494, + -0.013816828175460552, -0.000601566903700245, + 0.0013461011466981863, -0.04138724484724674, + 0.00873627850718282, -0.04087055918195267, + 0.02297560419816168, 0.02903447862864536, + 0.018738447708033793, -0.028635634859267987, + -0.02523056933250636, 0.0069597490485832775, + 0.011196171965906457, 0.036867967229670624, + 0.011711432703440066, -0.035202075090100315, + -0.01624426543168538, 0.005429740646329734, + -0.02527642753617346, 0.014590918876976148, + 0.011474643661906448, 0.01777462183262156, +}; +static const int flatmesh_shifts_nb = 128; +#define FLATMESH_SHIFTS_NB_STR "128" \ No newline at end of file diff --git a/src/controls/src/flatmeshnode.cpp b/src/controls/src/flatmeshnode.cpp deleted file mode 100644 index e711b0d..0000000 --- a/src/controls/src/flatmeshnode.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2016 Florent Revest - * All rights reserved. - * - * You may use this file under the terms of BSD license as follows: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the author nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "flatmeshnode.h" - -#include - -#include -#include - -/* Used to compute a triangle color from its distance to the center */ -static inline QColor interpolateColors(const QColor& color1, const QColor& color2, qreal ratio) -{ - /* Linear scale is too harsh, this looks better. This is not supposed to be called very often */ - ratio = pow(ratio, 1.7); - if (ratio>1) ratio=1; - - int r = color1.red()*(1-ratio) + color2.red()*ratio; - int g = color1.green()*(1-ratio) + color2.green()*ratio; - int b = color1.blue()*(1-ratio) + color2.blue()*ratio; - - return QColor(r, g, b); -} - -FlatMeshNode::FlatMeshNode(QQuickWindow *window, QRectF boundingRect) - : QSGSimpleRectNode(boundingRect, Qt::transparent), - m_animationState(0), m_animated(true), m_window(window) -{ - connect(window, SIGNAL(afterRendering()), this, SLOT(maybeAnimate())); - - connect(window, SIGNAL(widthChanged(int)), this, SLOT(generateGrid())); - connect(window, SIGNAL(heightChanged(int)), this, SLOT(generateGrid())); - - srand(time(NULL)); - generateGrid(); - - for(int y = 0; y < NUM_POINTS_Y-1; y++) { - for(int x = 0; x < NUM_POINTS_X-1; x++) { - for(int n = 0; n < 2; n++) { - QSGGeometryNode *triangle = new QSGGeometryNode(); - - QSGFlatColorMaterial *color = new QSGFlatColorMaterial; - triangle->setOpaqueMaterial(color); - triangle->setFlag(QSGNode::OwnsMaterial); - - QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 3); - triangle->setGeometry(geometry); - triangle->setFlag(QSGNode::OwnsGeometry); - - appendChildNode(triangle); - } - } - } - - maybeAnimate(); -} - -void FlatMeshNode::updateColors() -{ - int centerX = m_unitWidth*((NUM_POINTS_X-2)/2); - int centerY = m_unitHeight*((NUM_POINTS_Y-2)/2); - int radius = rect().width()*0.6; - - QSGGeometryNode *triangle = static_cast(firstChild()); - for(int y = 0; y < NUM_POINTS_Y-1; y++) { - for(int x = 0; x < NUM_POINTS_X-1; x++) { - for(int n = 0; n < 2; n++) { - QSGFlatColorMaterial *color = static_cast(triangle->opaqueMaterial()); - color->setColor(interpolateColors(m_centerColor, m_outerColor, - sqrt(pow(m_points[y*NUM_POINTS_Y+x].centerX-centerX, 2) + pow(m_points[y*NUM_POINTS_Y+x].centerY-centerY, 2))/radius)); - triangle->setOpaqueMaterial(color); - - triangle->markDirty(QSGNode::DirtyMaterial); - triangle = static_cast(triangle->nextSibling()); - } - } - } -} - -void FlatMeshNode::setCenterColor(QColor c) -{ - if (c == m_centerColor) - return; - m_centerColor = c; - updateColors(); -} - -void FlatMeshNode::setOuterColor(QColor c) -{ - if (c == m_outerColor) - return; - m_outerColor = c; - updateColors(); -} - -/* When the size changes, regenerate a grid of points that serves as a base for further operations */ -void FlatMeshNode::generateGrid() -{ - m_unitWidth = rect().width()/(NUM_POINTS_X-2); - m_unitHeight = rect().height()/(NUM_POINTS_Y-2); - - for(int y = 0; y < NUM_POINTS_Y; y++) { - for(int x = 0; x < NUM_POINTS_X; x++) { - Point *point = &m_points[y*NUM_POINTS_Y+x]; - point->centerX = m_unitWidth*x; - point->centerY = m_unitHeight*y; - - if(x != 0 && x != (NUM_POINTS_X-1) && y != 0 && y != (NUM_POINTS_Y-1)) { - int offsetX = rand()%m_unitWidth - m_unitWidth/3; - int offsetY = rand()%m_unitHeight - m_unitHeight/3; - float normalization = ((float)m_unitWidth)/(2*(abs(offsetX)+abs(offsetY))); - offsetX*=normalization; - offsetY*=normalization; - point->animOriginX = point->centerX + offsetX; - point->animOriginY = point->centerY + offsetY; - - offsetX = rand()%m_unitWidth - m_unitWidth/3; - offsetY = rand()%m_unitHeight - m_unitHeight/3; - normalization = ((float)m_unitWidth)/(2*(abs(offsetX)+abs(offsetY))); - offsetX*=normalization; - offsetY*=normalization; - point->animEndX = point->centerX + offsetX; - point->animEndY = point->centerY + offsetY; - } - else { - point->animEndX = point->animOriginX = point->centerX; - point->animEndY = point->animOriginY = point->centerY; - } - } - } -} - -void FlatMeshNode::setAnimated(bool animated) -{ - m_animated = animated; -} - -void FlatMeshNode::maybeAnimate() -{ - static QElapsedTimer t; - bool firstFrame = false; - if(!t.isValid()) { - t.start(); - firstFrame = true; - } - if (firstFrame || (m_animated && t.elapsed() >= 80)) { - t.restart(); - m_animationState += 0.03; - - /* Interpolate all points positions according to the animationState */ - for(int i = 0; i < NUM_POINTS_X*NUM_POINTS_Y; i++) { - Point *p = &m_points[i]; - - p->currentPos.x = p->animOriginX + (p->animEndX-p->animOriginX)*m_animationState; - p->currentPos.y = p->animOriginY + (p->animEndY-p->animOriginY)*m_animationState; - } - - /* Update all triangles' geometries according to the new points position */ - qreal lastCenterX = m_unitWidth*(NUM_POINTS_X-1); - qreal lastcenterY = m_unitHeight*(NUM_POINTS_Y-1); - QSGGeometryNode *triangle = static_cast(firstChild()); - for(int i = 0; i < NUM_POINTS_X*NUM_POINTS_Y; i++) { - if(m_points[i].centerX != lastCenterX && m_points[i].centerY != lastcenterY) { - QSGGeometry::Point2D *lowerV = triangle->geometry()->vertexDataAsPoint2D(); - lowerV[0] = m_points[i].currentPos; - lowerV[1] = m_points[i+NUM_POINTS_X].currentPos; - lowerV[2] = m_points[i+NUM_POINTS_X+1].currentPos; - triangle->markDirty(QSGNode::DirtyGeometry); - triangle = static_cast(triangle->nextSibling()); - - QSGGeometry::Point2D *upperV = triangle->geometry()->vertexDataAsPoint2D(); - upperV[0] = m_points[i].currentPos; - upperV[1] = m_points[i+1].currentPos; - upperV[2] = m_points[i+NUM_POINTS_X+1].currentPos; - triangle = static_cast(triangle->nextSibling()); - } - } - - /* Regenerate a set of animation end points when the animation is finished */ - if(m_animationState >= 1.0) { - m_animationState = 0.0; - - for(int y = 0; y < NUM_POINTS_Y; y++) { - for(int x = 0; x < NUM_POINTS_X; x++) { - Point *point = &m_points[y*NUM_POINTS_Y+x]; - - if(x != 0 && x != (NUM_POINTS_X-1) && y != 0 && y != (NUM_POINTS_Y-1)) { - int offsetX = rand()%m_unitWidth - m_unitWidth/3; - int offsetY = rand()%m_unitHeight - m_unitHeight/3; - float normalization = ((float)m_unitWidth)/(2*(abs(offsetX)+abs(offsetY))); - offsetX*=normalization; - offsetY*=normalization; - - point->animOriginX = point->animEndX; - point->animEndX = point->centerX + offsetX; - - point->animOriginY = point->animEndY; - point->animEndY = point->centerY + offsetY; - } - } - } - } - } -} diff --git a/src/controls/src/flatmeshnode.h b/src/controls/src/flatmeshnode.h deleted file mode 100644 index c0d01df..0000000 --- a/src/controls/src/flatmeshnode.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2016 Florent Revest - * All rights reserved. - * - * You may use this file under the terms of BSD license as follows: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the author nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef FLATMESHNODE_H -#define FLATMESHNODE_H - -#include -#include -#include - -#define NUM_POINTS_X 13 -#define NUM_POINTS_Y 13 - -struct Point { - qreal centerX; - qreal centerY; - - qreal animOriginX; - qreal animOriginY; - - qreal animEndX; - qreal animEndY; - - QSGGeometry::Point2D currentPos; -}; - -class FlatMeshNode : public QObject, public QSGSimpleRectNode -{ - Q_OBJECT -public: - FlatMeshNode(QQuickWindow *window, QRectF rect); - void setAnimated(bool animated); - - void setCenterColor(QColor c); - void setOuterColor(QColor c); - -public slots: - void maybeAnimate(); - void generateGrid(); - -private: - void updateColors(); - - qreal m_animationState; - bool m_animated; - int m_unitWidth, m_unitHeight; - QColor m_centerColor, m_outerColor; - QQuickWindow *m_window; - Point m_points[NUM_POINTS_X*NUM_POINTS_Y]; -}; - - -#endif // FLATMESHNODE_H diff --git a/src/controls/src/generate_flatmeshgeometry.py b/src/controls/src/generate_flatmeshgeometry.py new file mode 100755 index 0000000..c716fe0 --- /dev/null +++ b/src/controls/src/generate_flatmeshgeometry.py @@ -0,0 +1,135 @@ +#!/usr/bin/python3 +import pandas as pd +import numpy as np +import pyvista as pv +import math +import random + +# We can choose this constant to make the FlatMesh look more or less low-poly +nb_points = 100 + +# Find the radius of nb_points packed circles from www.packomania.com +radius_table = pd.read_csv("http://hydra.nat.uni-magdeburg.de/packing/cci/txt/radius.txt", header=None, sep=' ') +radius = float(radius_table[radius_table[0]==nb_points][1]) + +# Download the coordinates of nb_points optimally packed circles +points = pd.read_csv("http://hydra.nat.uni-magdeburg.de/packing/cci/txt/cci" + str(nb_points) + ".txt", sep=' ', + skipinitialspace=True, header=None).drop(columns=0).to_numpy() + +# Import these points as a 3D points cloud in PyVista +cloud = pv.PolyData(np.hstack((points, np.zeros((points.shape[0], 1))))) + +# Generate a Delaunay triangulation of this points cloud +mesh = cloud.delaunay_2d() + +# Find a series of triangle strips (see GL_TRIANGLE_STRIP) that describes this mesh +strips = mesh.strip() + +# Coordinates can be repeated if a point at x, y needs different color mixes as the last point of different triangles +# However, we try to de-duplicate vertices as much as we can and use an index array to point back to this buffer +vertices = [] +indices = [] + +# Each vertex is represented as a tuple of: x, y (its base coordinates, before any shift) and a color mixing ratio +def add_vertex(index, mix=None): + x, y = points[index] + + # Re-use existing vertices as much as possible + for i, vertex in enumerate(vertices): + if vertex[0] == x and vertex[1] == y: + if vertex[2] == None: + vertices[i] = (x, y, mix) + + if vertices[i][2] == mix or mix == None: + indices.append(i) + return + + # If we haven't found a vertex to recycle, create a new one + indices.append(len(vertices)) + vertices.append((x, y, mix)) + +# The color mixing ratio of a triangle depends on the distance of its baricenter to the center +def triangle_color_mix(index0, index1, index2): + x0, y0 = points[index0] + x1, y1 = points[index1] + x2, y2 = points[index2] + + x = (x0+x1+x2)/3 + y = (y0+y1+y2)/3 + + distance_to_center = math.sqrt(x**2 + y**2) + # Stretch the distance a little bit to use the full [0:1] range + distance_to_center *= 1.2 + + # Add a non-linearity (power 1.7) to make the gradient more interesting + mix = distance_to_center**1.7 + return min(mix, 1.0) + +# Iterate over all the strips found by PyVista +strip_len_index = 0 +for i in range(strips.n_strips): + strip_len = strips.strips[strip_len_index] + # The first two points of the strip don't need a color mix + add_vertex(strips.strips[strip_len_index+1]) + add_vertex(strips.strips[strip_len_index+2]) + # Iterate over all the other points of the strip + for i in range(strip_len_index+3, strip_len_index+1+strip_len): + index = strips.strips[i] + add_vertex(index, triangle_color_mix(strips.strips[i-2], strips.strips[i-1], index)) + + strip_len_index = strip_len_index + 1 + strip_len + + # Start a new strip using GL_PRIMITIVE_RESTART + if not strip_len_index == len(strips.strips): + indices.append(0xffff) + +# Generate a C++ header that contains the vertices/indices/shifts +out = open("flatmeshgeometry.h", "w") +out.write("// Do not modify manually! This file is generated by generate_flatmeshgeometry.py\n\n") + +# Output the vertices and their color mix attribute (VAO in OpenGL terminology) +out.write("static const QVector3D flatmesh_vertices[] = {\n") +for vertex in vertices: + x = vertex[0] + y = vertex[1] + mix = vertex[2] if vertex[2] is not None else 0.0 + out.write(" QVector3D(" + str(x/2) + ", " + str(y/2) + ", " + str(mix) + "),\n") +out.write("};\n") +out.write("static const int flatmesh_vertices_sz = " + str(len(vertices)) + ";\n\n") + +# Output the indices (EBO in OpenGL terminology) +out.write("static const unsigned short flatmesh_indices[] = {\n") +for index in indices: + out.write(" " + str(index) + ",\n") +out.write("};\n") +out.write("static const int flatmesh_indices_sz = " + str(len(indices)) + ";\n\n") + +# Pre-calculate a bunch of random shifts to save the watch some computing (https://xkcd.com/221/) +# A power of 2 here lets the compiler implement the modulo as a cheap AND bit-mask +random_shifts_nb = 128 +out.write("static const float flatmesh_shifts[] = {\n") +for i in range(random_shifts_nb): + # This sqrt() compensates the otherwise non-uniform probability distribution + r = radius * math.sqrt(random.random()) + alpha = 2 * math.pi * random.random() + + x = r * math.cos(alpha) + y = r * math.sin(alpha) + out.write(" " + str(x/2) + ", " + str(y/2) + ",\n") +out.write("};\n") +out.write("static const int flatmesh_shifts_nb = " + str(random_shifts_nb) + ";\n") +# For use by the inlined shader code +out.write("#define FLATMESH_SHIFTS_NB_STR \"" + str(random_shifts_nb) + "\"") + +out.close() + +# Output some statistics to make GPU memory usage more tractable +vertices_bytes = len(vertices)*(2*4+3) # Each vertex takes 2 floats and 3 chars +print(str(len(vertices)) + " vertices take " + str(vertices_bytes) + " bytes") +indices_bytes = len(indices)*2 # Each index takes 1 short +print(str(len(indices)) + " indices take " + str(indices_bytes) + " bytes") +shifts_bytes = random_shifts_nb*2*4 # Each shift takes 2 floats +print(str(random_shifts_nb) + " shifts take " + str(shifts_bytes) + " bytes") + +# With a total taking less than a page (4096B), we can be satisfied +print("Total takes " + str(vertices_bytes+indices_bytes+shifts_bytes) + " bytes")