File indexing completed on 2024-04-14 14:07:55

0001 /*************************************************************************************
0002  *  Copyright (C) 2012 by Percy Camilo T. Aucahuasi <percy.camilo.ta@gmail.com>      *
0003  *  Copyright (C) 2007 by Abderrahman Taha: Basic OpenGL calls like scene, lights    *
0004  *                                          and mouse behaviour taken from K3DSurf   *
0005  *                                                                                   *
0006  *  This program is free software; you can redistribute it and/or                    *
0007  *  modify it under the terms of the GNU General Public License                      *
0008  *  as published by the Free Software Foundation; either version 2                   *
0009  *  of the License, or (at your option) any later version.                           *
0010  *                                                                                   *
0011  *  This program is distributed in the hope that it will be useful,                  *
0012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
0013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
0014  *  GNU General Public License for more details.                                     *
0015  *                                                                                   *
0016  *  You should have received a copy of the GNU General Public License                *
0017  *  along with this program; if not, write to the Free Software                      *
0018  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
0019  *************************************************************************************/
0020 
0021 #include "plotter3d_es.h"
0022 
0023 #include "surface.h"
0024 #include "spacecurve.h"
0025 
0026 #include "plotsmodel.h"
0027 #include "private/utils/mathutils.h"
0028 #include "private/export3d.h"
0029 
0030 #include <cmath>
0031 #include <QDebug>
0032 #include <QFile>
0033 #include <QPixmap>
0034 #include <QUrl>
0035 #include <QPrinter>
0036 #include <QPainter>
0037 
0038 #if defined(HAVE_IEEEFP_H)
0039 #include <ieeefp.h>
0040 // bool isinf(double x) { return !finite(x) && x==x; }
0041 #endif
0042 
0043 using namespace std;
0044 using namespace Analitza;
0045 
0046 const GLubyte Plotter3DES::XAxisArrowColor[] = {250 -1, 1, 1};
0047 const GLubyte Plotter3DES::YAxisArrowColor[] = {1, 255 - 1, 1};
0048 const GLubyte Plotter3DES::ZAxisArrowColor[] = {1, 1, 255 - 1};
0049 
0050 static QOpenGLBuffer createSurfaceBuffer(Surface* surf)
0051 {
0052     Q_ASSERT(QOpenGLContext::currentContext());
0053 
0054     QOpenGLBuffer buffer(QOpenGLBuffer::VertexBuffer);
0055     buffer.create();
0056     buffer.bind();
0057 
0058     const auto offsetNormal = 3*sizeof(float)*surf->vertices().size();
0059     const auto offsetEnd = offsetNormal + 3*sizeof(float)*surf->normals().size();
0060     buffer.allocate(surf->vertices().constData(), offsetEnd);
0061     buffer.write(offsetNormal, surf->normals().constData(), 3*sizeof(float)*surf->normals().size());
0062 
0063     Q_ASSERT(buffer.isCreated());
0064     buffer.release();
0065 
0066     return buffer;
0067 }
0068 
0069 Plotter3DES::Plotter3DES(QAbstractItemModel* model)
0070     : m_model(model)
0071     , m_plotStyle(Solid)
0072     , m_plottingFocusPolicy(All)
0073     , m_depth(-5)
0074     , m_scale(60)
0075     , m_currentAxisIndicator(InvalidAxis)
0076     , m_simpleRotation(false)
0077     , m_referencePlaneColor(Qt::darkGray)
0078 {
0079     resetViewPrivate(QVector3D(-45, 0, -135));
0080 }
0081 
0082 Plotter3DES::~Plotter3DES()
0083 {
0084     for (int i = 0; i < m_itemGeometries.size(); ++i)
0085     {
0086         m_itemGeometries.take(itemAt(i)).destroy();
0087     }
0088 
0089 //     glDeleteLists(m_sceneObjects.value(RefPlaneXY), 1);
0090 //     glDeleteLists(m_sceneObjects.value(XArrowAxisHint), 1);
0091 //     glDeleteLists(m_sceneObjects.value(YArrowAxisHint), 1);
0092 //     glDeleteLists(m_sceneObjects.value(ZArrowAxisHint), 1);
0093 }
0094 
0095 void Plotter3DES::initGL()
0096 {
0097     initializeOpenGLFunctions();
0098 
0099     //TODO: provide GLSL version
0100     program.addShaderFromSourceCode(QOpenGLShader::Vertex,
0101         "attribute highp vec4 vertex;\n"
0102         "attribute highp vec4 normal;\n"
0103         "uniform highp mat4 matrix;\n"
0104 
0105         "void main(void)\n"
0106         "{\n"
0107         "   gl_Position = matrix * vertex;\n"
0108         "}"
0109     );
0110     program.addShaderFromSourceCode(QOpenGLShader::Fragment,
0111         "uniform mediump vec4 color;\n"
0112 
0113         "void main(void)\n"
0114         "{\n"
0115         "   highp float w = 10.*gl_FragCoord.w;\n"
0116         "   highp vec4 zvec = vec4(w, w, w, 1.0);"
0117         "   gl_FragColor = mix(color, zvec, vec4(.5,.5,.5,1.));\n"
0118         "}"
0119     );
0120     program.link();
0121 
0122     if (m_model && m_model->rowCount() > 0) {
0123         updatePlots(QModelIndex(), 0, m_model->rowCount()-1);
0124     }
0125 }
0126 
0127 void Plotter3DES::resetViewport()
0128 {
0129     m_rot.setToIdentity();
0130     resetViewPrivate(QVector3D(-45, 0, -135));
0131     renderGL();
0132 }
0133 
0134 void Plotter3DES::resetViewPrivate(const QVector3D& rot)
0135 {
0136     m_rot.translate(0,0, -20);
0137     m_rot.rotate(rot.x(), 1, 0, 0);
0138     m_rot.rotate(rot.y(), 0, 1, 0);
0139     m_rot.rotate(rot.z(), 0, 0, 1);
0140     m_simpleRotationVector = rot;
0141 }
0142 
0143 void Plotter3DES::setViewport(const QRectF& vp)
0144 {
0145     m_viewport = vp;
0146 
0147     m_projection.setToIdentity();
0148     m_projection.perspective(m_scale, m_viewport.width()/m_viewport.height(), 0.1, 3000);
0149 
0150     renderGL();
0151 }
0152 
0153 void Plotter3DES::drawPlots()
0154 {
0155     glClearColor(0, 0, 0, 1);
0156     glEnable(GL_DEPTH_TEST);
0157     glDepthMask(true);
0158 
0159     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
0160 
0161     if(!m_model)
0162     {
0163         return;
0164     }
0165 
0166     program.bind();
0167     program.setUniformValue("matrix", m_projection * m_rot);
0168 
0169     const int vertexLocation = program.attributeLocation("vertex");
0170     const int normalLocation = program.attributeLocation("normal");
0171     GLenum mode;
0172     switch (m_plotStyle)
0173     {
0174         case Solid: mode = GL_TRIANGLES; break;
0175         case Wired: mode = GL_LINES; break;
0176         case Dots:  mode = GL_POINTS; break;
0177     }
0178 
0179     drawAxes();
0180     drawRefPlane();
0181     program.enableAttributeArray(vertexLocation);
0182     for (int i = 0, c = m_model->rowCount(); i < c; ++i)
0183     {
0184         PlotItem *item = itemAt(i);
0185 
0186         if (!item || item->spaceDimension() != Dim3D || !item->isVisible())
0187             continue;
0188 
0189         program.setUniformValue("color", item->color());
0190         if (SpaceCurve *curv = dynamic_cast<SpaceCurve*>(item))
0191         {
0192             //TODO: implement jumps
0193             program.setAttributeArray(vertexLocation, GL_FLOAT, curv->points().constData(), 3);
0194             glDrawArrays(GL_LINE_STRIP, 0, curv->points().size());
0195         }
0196         else if (Surface *surf = dynamic_cast<Surface*>(item))
0197         {
0198             QOpenGLBuffer it;
0199             if(!m_itemGeometries.contains(surf)) {
0200                 m_itemGeometries[surf] = it = createSurfaceBuffer(surf);
0201             } else {
0202                 it = m_itemGeometries[surf];
0203             }
0204 
0205             Q_ASSERT(it.isCreated());
0206 
0207             program.enableAttributeArray(normalLocation);
0208             it.bind();
0209 
0210             const auto offsetNormal = 3*sizeof(float)*surf->vertices().size();
0211 
0212             program.setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3);
0213             program.setAttributeBuffer(normalLocation, GL_FLOAT, offsetNormal, 3);
0214             glDrawElements(mode, surf->indexes().size(), GL_UNSIGNED_INT, surf->indexes().constData());
0215 
0216             if (mode == GL_TRIANGLES) {
0217                 program.setUniformValue("color", QColor(Qt::black));
0218                 glDrawElements(GL_POINTS, surf->indexes().size(), GL_UNSIGNED_INT, surf->indexes().constData());
0219             }
0220             it.release();
0221             program.disableAttributeArray(normalLocation);
0222         }
0223     }
0224     program.disableAttributeArray(vertexLocation);
0225     program.release();
0226 }
0227 
0228 void Plotter3DES::exportSurfaces(const QString& path) const
0229 {
0230     Export3D::exportX3D(path, m_model);
0231 }
0232 
0233 void Plotter3DES::updatePlots(const QModelIndex & parent, int s, int e)
0234 {
0235     Q_ASSERT(!parent.isValid());
0236     Q_UNUSED(parent);
0237 
0238     for(int i=s; i<=e; i++) {
0239         PlotItem *item = itemAt(i);
0240 
0241         if (!item)
0242             return;
0243 
0244         m_itemGeometries.take(item).destroy();
0245 
0246         if (item->isVisible()) {
0247             //         addFuncs(QModelIndex(), s.row(), s.row());
0248             //igual no usar addFuncs sino la funcion interna pues no actualiza los items si tienen data
0249             addPlots(item);
0250         }
0251     }
0252 
0253     const int count = m_model->rowCount();
0254     if (count <= e)
0255     {
0256         for (int i = e; i < count; ++i)
0257         {
0258             m_itemGeometries.take(itemAt(i)).destroy();
0259         }
0260     }
0261 
0262     renderGL();
0263 }
0264 
0265 void Plotter3DES::setModel(QAbstractItemModel* f)
0266 {
0267     m_model = f;
0268 
0269     if (m_model)
0270         updatePlots(QModelIndex(), 0, m_model->rowCount()-1);
0271     modelChanged();
0272 }
0273 
0274 void Plotter3DES::setPlottingFocusPolicy(PlottingFocusPolicy fp)
0275 {
0276     m_plottingFocusPolicy = fp;
0277 
0278     for (int i = 0; i < m_itemGeometries.size(); ++i)
0279     {
0280         m_itemGeometries.take(itemAt(i)).destroy();
0281     }
0282 
0283     updatePlots(QModelIndex(), 0, m_model->rowCount()-1);
0284 }
0285 
0286 void Plotter3DES::scale(qreal factor)
0287 {
0288     m_scale = qBound(1., factor*m_scale, 140.);
0289 
0290     setViewport(m_viewport);
0291 }
0292 
0293 void Plotter3DES::setUseSimpleRotation(bool simplerot)
0294 {
0295     m_simpleRotation = simplerot;
0296 }
0297 
0298 void Plotter3DES::rotate(int dx, int dy)
0299 {
0300     const qreal ax = -dy;
0301     const qreal ay = -dx;
0302     const double angle = sqrt(ax*ax + ay*ay)/(m_viewport.width() + 1)*360.0;
0303 
0304     if (m_simpleRotation) {
0305         m_rot.setToIdentity();
0306         resetViewPrivate(m_simpleRotationVector + QVector3D(ax, 0, ay));
0307         renderGL();
0308     } else if (!m_rotFixed.isNull()) {
0309         m_rot.rotate(angle, m_rotFixed.normalized());
0310         renderGL();
0311     } else {
0312 //         TODO: figure out how to do this on an opengl es compatible way
0313 
0314 //         GLfloat matrix[16] = {0}; // model view matrix from current OpenGL state
0315 //
0316 //         glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
0317 //
0318 //         QMatrix4x4 matrix4(matrix, 4, 4);
0319 //         bool couldInvert;
0320 //         matrix4 = matrix4.inverted(&couldInvert);
0321 //
0322 //         if (couldInvert) {
0323 //             QVector3D rot(matrix4.row(0).x()*ax + matrix4.row(1).x()*ay,
0324 //                           matrix4.row(0).y()*ax + matrix4.row(1).y()*ay,
0325 //                           matrix4.row(0).z()*ax + matrix4.row(1).z()*ay);
0326 //
0327 //             m_rot.rotate(rot.length(), rot.normalized());
0328 //
0329 //             renderGL();
0330 //         }
0331     }
0332 }
0333 
0334 CartesianAxis Plotter3DES::selectAxisArrow(int x, int y)
0335 {
0336     GLint viewport[4];
0337     GLubyte pixel[3];
0338 
0339     glGetIntegerv(GL_VIEWPORT,viewport);
0340 
0341     glReadPixels(x, viewport[3]-y, 1, 1, GL_RGB,GL_UNSIGNED_BYTE,(void *)pixel);
0342 
0343     if (memcmp(pixel, XAxisArrowColor, sizeof(pixel)) == 0) return XAxis;
0344     if (memcmp(pixel, YAxisArrowColor, sizeof(pixel)) == 0) return YAxis;
0345     if (memcmp(pixel, ZAxisArrowColor, sizeof(pixel)) == 0) return ZAxis;
0346 
0347     return InvalidAxis;
0348 }
0349 
0350 void Plotter3DES::fixRotation(const QVector3D& vec)
0351 {
0352     m_rotFixed = vec;
0353 }
0354 
0355 void Plotter3DES::showAxisArrowHint(CartesianAxis axis)
0356 {
0357     if (axis == InvalidAxis)
0358         return ;
0359 
0360     m_currentAxisIndicator = axis;
0361 
0362     renderGL();
0363 }
0364 
0365 void Plotter3DES::hideAxisHint()
0366 {
0367     m_currentAxisIndicator = InvalidAxis;
0368 
0369     renderGL();
0370 }
0371 
0372 void Plotter3DES::addPlots(PlotItem* item)
0373 {
0374     Q_ASSERT(item);
0375 
0376     if (SpaceCurve *curve = dynamic_cast<SpaceCurve*>(item))
0377     {
0378         if (curve->points().isEmpty())
0379             curve->update(QVector3D(), QVector3D());
0380     }
0381     else if (Surface* surf = dynamic_cast<Surface*>(item))
0382     {
0383         if (surf->indexes().isEmpty())
0384             surf->update(QVector3D(), QVector3D());
0385 
0386         Q_ASSERT(!surf->indexes().isEmpty());
0387     }
0388 }
0389 
0390 PlotItem* Plotter3DES::itemAt(int row) const
0391 {
0392     QModelIndex pi = m_model->index(row, 0);
0393 
0394     if (!pi.isValid())
0395         return nullptr;
0396 
0397     PlotItem* plot = pi.data(PlotsModel::PlotRole).value<PlotItem*>();
0398 
0399     if (plot->spaceDimension() != Dim3D)
0400         return nullptr;
0401 
0402     return plot;
0403 }
0404 
0405 void Plotter3DES::drawAxes()
0406 {
0407     glLineWidth(1.5f);
0408 
0409     static GLfloat const xVertices[] = {
0410         0.f, 0.f, 0.f,
0411         10.f, 0.f, 0.f,
0412     };
0413     static GLfloat const yVertices[] = {
0414         0.f, 0.f, 0.f,
0415         0.f, 10.f, 0.f,
0416     };
0417     static GLfloat const zVertices[] = {
0418         0.f, 0.f, 0.f,
0419         0.f, 0.f, 10.f,
0420     };
0421 
0422     static QVector<GLuint> const idxs = {0,1};
0423 
0424     const int vertexLocation = program.attributeLocation("vertex");
0425     program.enableAttributeArray(vertexLocation);
0426 
0427     program.setUniformValue("color", QColor(Qt::red));
0428     program.setAttributeArray(vertexLocation, xVertices, 3);
0429     glDrawElements(GL_LINES, idxs.size(), GL_UNSIGNED_INT, idxs.constData());
0430 
0431     static GLfloat const XArrowVertices[] = {
0432         10.f,   0.f, 0.f,
0433 
0434         9.8f,  0.1f, 0.f,
0435         9.8f,   0.f, 0.1f,
0436         9.8f, -0.1f, 0.f,
0437         9.8f,   0.f, -0.1f,
0438         9.8f,  0.1f, 0.f
0439     };
0440     program.setAttributeArray(vertexLocation, XArrowVertices, 3);
0441     glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
0442 
0443     program.setUniformValue("color", QColor(Qt::green));
0444     program.setAttributeArray(vertexLocation, yVertices, 3);
0445     glDrawElements(GL_LINES, idxs.size(), GL_UNSIGNED_INT, idxs.constData());
0446 
0447     static GLfloat const YArrowVertices[] = {
0448            0.f, 10.f,  0.f,
0449 
0450           0.1f, 9.8f,  0.f,
0451            0.f, 9.8f, 0.1f,
0452          -0.1f, 9.8f,  0.f,
0453            0.f, 9.8f,-0.1f,
0454           0.1f, 9.8f,  0.f
0455     };
0456     program.setAttributeArray(vertexLocation, YArrowVertices, 3);
0457     glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
0458 
0459     program.setUniformValue("color", QColor(Qt::blue));
0460     program.setAttributeArray(vertexLocation, zVertices, 3);
0461     glDrawElements(GL_LINES, idxs.size(), GL_UNSIGNED_INT, idxs.constData());
0462 
0463     static GLfloat const ZArrowVertices[] = {
0464            0.f,  0.f, 10.f,
0465           0.1f,  0.f, 9.8f,
0466            0.f, 0.1f, 9.8f,
0467          -0.1f,  0.f, 9.8f,
0468            0.f,-0.1f, 9.8f,
0469           0.1f,  0.f, 9.8f
0470     };
0471     program.setAttributeArray(vertexLocation, ZArrowVertices, 3);
0472     glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
0473 
0474     program.disableAttributeArray(vertexLocation);
0475 }
0476 
0477 void Plotter3DES::drawRefPlane()
0478 {
0479     glLineWidth(1.f);
0480     const float lims = 10;
0481     QVector<QVector3D> vxs;
0482 
0483     for(float x=-lims; x<=lims; ++x) {
0484         vxs += { x, -lims, m_depth };
0485         vxs += { x, lims, m_depth };
0486     }
0487 
0488     for(float y=-lims; y<=lims; ++y) {
0489         vxs += { -lims, y, m_depth };
0490         vxs += { lims, y, m_depth };
0491     }
0492 
0493     const int vertexLocation = program.attributeLocation("vertex");
0494     program.enableAttributeArray(vertexLocation);
0495     program.setUniformValue("color", m_referencePlaneColor);
0496     program.setAttributeArray(vertexLocation, GL_FLOAT, vxs.constData(), 3);
0497     glDrawArrays(GL_LINES, 0, vxs.size());
0498     program.disableAttributeArray(vertexLocation);
0499 }
0500 
0501 bool Plotter3DES::save(const QUrl& url)
0502 {
0503     if (!url.isLocalFile())
0504         return false;
0505 
0506     const QString path = url.toLocalFile();
0507     if(path.endsWith(QLatin1String(".x3d")) || path.endsWith(QLatin1String(".stl"))) {
0508         exportSurfaces(path);
0509     } else if(path.endsWith(QLatin1String(".pdf"))) {
0510         auto px = grabImage();
0511 //this pulls widgets
0512         QPrinter printer;
0513         printer.setOutputFormat(QPrinter::PdfFormat);
0514         printer.setOutputFileName(path);
0515 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0516         printer.setPaperSize(px.size(), QPrinter::DevicePixel);
0517 #else
0518 #pragma "port printer.setPaperSize";
0519 #endif
0520         printer.setPageMargins(QMarginsF(0,0,0,0));
0521         QPainter painter;
0522         painter.begin(&printer);
0523         painter.drawImage(QPoint(0,0), px);
0524         painter.end();
0525     } else {
0526         auto px = grabImage();
0527         return px.save(path);
0528     }
0529     return true;
0530 }
0531 
0532 QStringList Plotter3DES::filters() const
0533 {
0534     return {QObject::tr("PNG Image (*.png)"), QObject::tr("PDF Document (*.pdf)"), QObject::tr("X3D Document (*.x3d)"), QObject::tr("STL Document (*.stl)")};
0535 }