| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2017 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the examples of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:BSD$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** BSD License Usage |
| 18 | ** Alternatively, you may use this file under the terms of the BSD license |
| 19 | ** as follows: |
| 20 | ** |
| 21 | ** "Redistribution and use in source and binary forms, with or without |
| 22 | ** modification, are permitted provided that the following conditions are |
| 23 | ** met: |
| 24 | ** * Redistributions of source code must retain the above copyright |
| 25 | ** notice, this list of conditions and the following disclaimer. |
| 26 | ** * Redistributions in binary form must reproduce the above copyright |
| 27 | ** notice, this list of conditions and the following disclaimer in |
| 28 | ** the documentation and/or other materials provided with the |
| 29 | ** distribution. |
| 30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
| 31 | ** contributors may be used to endorse or promote products derived |
| 32 | ** from this software without specific prior written permission. |
| 33 | ** |
| 34 | ** |
| 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
| 46 | ** |
| 47 | ** $QT_END_LICENSE$ |
| 48 | ** |
| 49 | ****************************************************************************/ |
| 50 | |
| 51 | #include <QtGui/QImage> |
| 52 | #include <qmath.h> |
| 53 | #include "glwidget.h" |
| 54 | |
| 55 | #ifndef GL_MULTISAMPLE |
| 56 | #define GL_MULTISAMPLE 0x809D |
| 57 | #endif |
| 58 | |
| 59 | GLWidget::GLWidget(QWidget *parent) |
| 60 | : QGLWidget(QGLFormat(QGL::SampleBuffers|QGL::AlphaChannel), parent) |
| 61 | { |
| 62 | setWindowTitle(tr(s: "OpenGL framebuffer objects" )); |
| 63 | makeCurrent(); |
| 64 | |
| 65 | if (QGLFramebufferObject::hasOpenGLFramebufferBlit()) { |
| 66 | QGLFramebufferObjectFormat format; |
| 67 | format.setSamples(4); |
| 68 | format.setAttachment(QGLFramebufferObject::CombinedDepthStencil); |
| 69 | |
| 70 | render_fbo = new QGLFramebufferObject(512, 512, format); |
| 71 | texture_fbo = new QGLFramebufferObject(512, 512); |
| 72 | } else { |
| 73 | render_fbo = new QGLFramebufferObject(1024, 1024); |
| 74 | texture_fbo = render_fbo; |
| 75 | } |
| 76 | |
| 77 | rot_x = rot_y = rot_z = 0.0f; |
| 78 | scale = 0.1f; |
| 79 | anim = new QTimeLine(750, this); |
| 80 | anim->setUpdateInterval(20); |
| 81 | connect(asender: anim, SIGNAL(valueChanged(qreal)), SLOT(animate(qreal))); |
| 82 | connect(asender: anim, SIGNAL(finished()), SLOT(animFinished())); |
| 83 | |
| 84 | svg_renderer = new QSvgRenderer(QLatin1String(":/res/bubbles.svg" ), this); |
| 85 | connect(sender: svg_renderer, SIGNAL(repaintNeeded()), receiver: this, SLOT(draw())); |
| 86 | |
| 87 | logo = QImage(":/res/designer.png" ); |
| 88 | logo = logo.convertToFormat(f: QImage::Format_ARGB32); |
| 89 | |
| 90 | tile_list = glGenLists(range: 1); |
| 91 | glNewList(list: tile_list, GL_COMPILE); |
| 92 | glBegin(GL_QUADS); |
| 93 | { |
| 94 | glTexCoord2f(s: 0.0f, t: 0.0f); glVertex3f(x: -1.0f, y: -1.0f, z: 1.0f); |
| 95 | glTexCoord2f(s: 1.0f, t: 0.0f); glVertex3f( x: 1.0f, y: -1.0f, z: 1.0f); |
| 96 | glTexCoord2f(s: 1.0f, t: 1.0f); glVertex3f( x: 1.0f, y: 1.0f, z: 1.0f); |
| 97 | glTexCoord2f(s: 0.0f, t: 1.0f); glVertex3f(x: -1.0f, y: 1.0f, z: 1.0f); |
| 98 | |
| 99 | glTexCoord2f(s: 1.0f, t: 0.0f); glVertex3f(x: -1.0f, y: -1.0f, z: -1.0f); |
| 100 | glTexCoord2f(s: 1.0f, t: 1.0f); glVertex3f(x: -1.0f, y: 1.0f, z: -1.0f); |
| 101 | glTexCoord2f(s: 0.0f, t: 1.0f); glVertex3f( x: 1.0f, y: 1.0f, z: -1.0f); |
| 102 | glTexCoord2f(s: 0.0f, t: 0.0f); glVertex3f( x: 1.0f, y: -1.0f, z: -1.0f); |
| 103 | |
| 104 | glTexCoord2f(s: 0.0f, t: 1.0f); glVertex3f(x: -1.0f, y: 1.0f, z: -1.0f); |
| 105 | glTexCoord2f(s: 0.0f, t: 0.0f); glVertex3f(x: -1.0f, y: 1.0f, z: 1.0f); |
| 106 | glTexCoord2f(s: 1.0f, t: 0.0f); glVertex3f( x: 1.0f, y: 1.0f, z: 1.0f); |
| 107 | glTexCoord2f(s: 1.0f, t: 1.0f); glVertex3f( x: 1.0f, y: 1.0f, z: -1.0f); |
| 108 | |
| 109 | glTexCoord2f(s: 1.0f, t: 1.0f); glVertex3f(x: -1.0f, y: -1.0f, z: -1.0f); |
| 110 | glTexCoord2f(s: 0.0f, t: 1.0f); glVertex3f( x: 1.0f, y: -1.0f, z: -1.0f); |
| 111 | glTexCoord2f(s: 0.0f, t: 0.0f); glVertex3f( x: 1.0f, y: -1.0f, z: 1.0f); |
| 112 | glTexCoord2f(s: 1.0f, t: 0.0f); glVertex3f(x: -1.0f, y: -1.0f, z: 1.0f); |
| 113 | |
| 114 | glTexCoord2f(s: 1.0f, t: 0.0f); glVertex3f( x: 1.0f, y: -1.0f, z: -1.0f); |
| 115 | glTexCoord2f(s: 1.0f, t: 1.0f); glVertex3f( x: 1.0f, y: 1.0f, z: -1.0f); |
| 116 | glTexCoord2f(s: 0.0f, t: 1.0f); glVertex3f( x: 1.0f, y: 1.0f, z: 1.0f); |
| 117 | glTexCoord2f(s: 0.0f, t: 0.0f); glVertex3f( x: 1.0f, y: -1.0f, z: 1.0f); |
| 118 | |
| 119 | glTexCoord2f(s: 0.0f, t: 0.0f); glVertex3f(x: -1.0f, y: -1.0f, z: -1.0f); |
| 120 | glTexCoord2f(s: 1.0f, t: 0.0f); glVertex3f(x: -1.0f, y: -1.0f, z: 1.0f); |
| 121 | glTexCoord2f(s: 1.0f, t: 1.0f); glVertex3f(x: -1.0f, y: 1.0f, z: 1.0f); |
| 122 | glTexCoord2f(s: 0.0f, t: 1.0f); glVertex3f(x: -1.0f, y: 1.0f, z: -1.0f); |
| 123 | } |
| 124 | glEnd(); |
| 125 | glEndList(); |
| 126 | |
| 127 | wave = new GLfloat[logo.width()*logo.height()]; |
| 128 | memset(s: wave, c: 0, n: logo.width()*logo.height()); |
| 129 | startTimer(interval: 30); // wave timer |
| 130 | } |
| 131 | |
| 132 | GLWidget::~GLWidget() |
| 133 | { |
| 134 | delete[] wave; |
| 135 | glDeleteLists(list: tile_list, range: 1); |
| 136 | delete texture_fbo; |
| 137 | if (render_fbo != texture_fbo) |
| 138 | delete render_fbo; |
| 139 | } |
| 140 | |
| 141 | void GLWidget::paintEvent(QPaintEvent *) |
| 142 | { |
| 143 | draw(); |
| 144 | } |
| 145 | |
| 146 | void GLWidget::draw() |
| 147 | { |
| 148 | QPainter p(this); // used for text overlay |
| 149 | |
| 150 | // save the GL state set for QPainter |
| 151 | saveGLState(); |
| 152 | |
| 153 | // render the 'bubbles.svg' file into our framebuffer object |
| 154 | QPainter fbo_painter(render_fbo); |
| 155 | svg_renderer->render(p: &fbo_painter); |
| 156 | fbo_painter.end(); |
| 157 | |
| 158 | if (render_fbo != texture_fbo) { |
| 159 | QRect rect(0, 0, render_fbo->width(), render_fbo->height()); |
| 160 | QGLFramebufferObject::blitFramebuffer(target: texture_fbo, targetRect: rect, |
| 161 | source: render_fbo, sourceRect: rect); |
| 162 | } |
| 163 | |
| 164 | // draw into the GL widget |
| 165 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| 166 | glMatrixMode(GL_PROJECTION); |
| 167 | glLoadIdentity(); |
| 168 | glFrustum(left: -1, right: 1, bottom: -1, top: 1, near_val: 10, far_val: 100); |
| 169 | glTranslatef(x: 0.0f, y: 0.0f, z: -15.0f); |
| 170 | glMatrixMode(GL_MODELVIEW); |
| 171 | glLoadIdentity(); |
| 172 | glViewport(x: 0, y: 0, width: width(), height: height()); |
| 173 | glEnable(GL_BLEND); |
| 174 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| 175 | |
| 176 | glBindTexture(GL_TEXTURE_2D, texture: texture_fbo->texture()); |
| 177 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| 178 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 179 | glEnable(GL_TEXTURE_2D); |
| 180 | glEnable(GL_MULTISAMPLE); |
| 181 | glEnable(GL_CULL_FACE); |
| 182 | |
| 183 | // draw background |
| 184 | glPushMatrix(); |
| 185 | glScalef(x: 1.7f, y: 1.7f, z: 1.7f); |
| 186 | glColor4f(red: 1.0f, green: 1.0f, blue: 1.0f, alpha: 1.0f); |
| 187 | glCallList(list: tile_list); |
| 188 | glPopMatrix(); |
| 189 | |
| 190 | const int w = logo.width(); |
| 191 | const int h = logo.height(); |
| 192 | |
| 193 | glRotatef(angle: rot_x, x: 1.0f, y: 0.0f, z: 0.0f); |
| 194 | glRotatef(angle: rot_y, x: 0.0f, y: 1.0f, z: 0.0f); |
| 195 | glRotatef(angle: rot_z, x: 0.0f, y: 0.0f, z: 1.0f); |
| 196 | glScalef(x: scale/w, y: scale/w, z: scale/w); |
| 197 | |
| 198 | glDepthFunc(GL_LESS); |
| 199 | glEnable(GL_DEPTH_TEST); |
| 200 | // draw the Qt icon |
| 201 | glTranslatef(x: -w+1, y: -h+1, z: 0.0f); |
| 202 | for (int y=h-1; y>=0; --y) { |
| 203 | uint *p = (uint*) logo.scanLine(y); |
| 204 | uint *end = p + w; |
| 205 | int x = 0; |
| 206 | while (p < end) { |
| 207 | glColor4ub(red: qRed(rgb: *p), green: qGreen(rgb: *p), blue: qBlue(rgb: *p), alpha: uchar(qAlpha(rgb: *p)*.9)); |
| 208 | glTranslatef(x: 0.0f, y: 0.0f, z: wave[y*w+x]); |
| 209 | if (qAlpha(rgb: *p) > 128) |
| 210 | glCallList(list: tile_list); |
| 211 | glTranslatef(x: 0.0f, y: 0.0f, z: -wave[y*w+x]); |
| 212 | glTranslatef(x: 2.0f, y: 0.0f, z: 0.0f); |
| 213 | ++x; |
| 214 | ++p; |
| 215 | } |
| 216 | glTranslatef(x: -w*2.0f, y: 2.0f, z: 0.0f); |
| 217 | } |
| 218 | |
| 219 | // restore the GL state that QPainter expects |
| 220 | restoreGLState(); |
| 221 | |
| 222 | // draw the overlayed text using QPainter |
| 223 | p.setPen(QColor(197, 197, 197, 157)); |
| 224 | p.setBrush(QColor(197, 197, 197, 127)); |
| 225 | p.drawRect(r: QRect(0, 0, width(), 50)); |
| 226 | p.setPen(Qt::black); |
| 227 | p.setBrush(Qt::NoBrush); |
| 228 | const QString str1(tr(s: "A simple OpenGL framebuffer object example." )); |
| 229 | const QString str2(tr(s: "Use the mouse wheel to zoom, press buttons and move mouse to rotate, double-click to flip." )); |
| 230 | QFontMetrics fm(p.font()); |
| 231 | p.drawText(x: width()/2 - fm.horizontalAdvance(str1)/2, y: 20, s: str1); |
| 232 | p.drawText(x: width()/2 - fm.horizontalAdvance(str2)/2, y: 20 + fm.lineSpacing(), s: str2); |
| 233 | } |
| 234 | |
| 235 | void GLWidget::mousePressEvent(QMouseEvent *e) |
| 236 | { |
| 237 | anchor = e->pos(); |
| 238 | } |
| 239 | |
| 240 | void GLWidget::mouseMoveEvent(QMouseEvent *e) |
| 241 | { |
| 242 | QPoint diff = e->pos() - anchor; |
| 243 | if (e->buttons() & Qt::LeftButton) { |
| 244 | rot_x += diff.y()/5.0f; |
| 245 | rot_y += diff.x()/5.0f; |
| 246 | } else if (e->buttons() & Qt::RightButton) { |
| 247 | rot_z += diff.x()/5.0f; |
| 248 | } |
| 249 | |
| 250 | anchor = e->pos(); |
| 251 | draw(); |
| 252 | } |
| 253 | |
| 254 | void GLWidget::wheelEvent(QWheelEvent *e) |
| 255 | { |
| 256 | e->angleDelta().y() > 0 ? scale += scale*0.1f : scale -= scale*0.1f; |
| 257 | draw(); |
| 258 | } |
| 259 | |
| 260 | void GLWidget::mouseDoubleClickEvent(QMouseEvent *) |
| 261 | { |
| 262 | anim->start(); |
| 263 | } |
| 264 | |
| 265 | void GLWidget::animate(qreal val) |
| 266 | { |
| 267 | rot_y = val * 180; |
| 268 | draw(); |
| 269 | } |
| 270 | |
| 271 | void GLWidget::animFinished() |
| 272 | { |
| 273 | if (anim->direction() == QTimeLine::Forward) |
| 274 | anim->setDirection(QTimeLine::Backward); |
| 275 | else |
| 276 | anim->setDirection(QTimeLine::Forward); |
| 277 | } |
| 278 | |
| 279 | void GLWidget::saveGLState() |
| 280 | { |
| 281 | glPushAttrib(GL_ALL_ATTRIB_BITS); |
| 282 | glMatrixMode(GL_PROJECTION); |
| 283 | glPushMatrix(); |
| 284 | glMatrixMode(GL_MODELVIEW); |
| 285 | glPushMatrix(); |
| 286 | } |
| 287 | |
| 288 | void GLWidget::restoreGLState() |
| 289 | { |
| 290 | glMatrixMode(GL_PROJECTION); |
| 291 | glPopMatrix(); |
| 292 | glMatrixMode(GL_MODELVIEW); |
| 293 | glPopMatrix(); |
| 294 | glPopAttrib(); |
| 295 | } |
| 296 | |
| 297 | void GLWidget::timerEvent(QTimerEvent *) |
| 298 | { |
| 299 | if (QApplication::mouseButtons() != 0) |
| 300 | return; |
| 301 | |
| 302 | static bool scale_in = true; |
| 303 | |
| 304 | if (scale_in && scale > 35.0f) |
| 305 | scale_in = false; |
| 306 | else if (!scale_in && scale < .5f) |
| 307 | scale_in = true; |
| 308 | |
| 309 | scale *= scale_in ? 1.01f : 0.99f; |
| 310 | rot_z += 0.3f; |
| 311 | rot_x += 0.1f; |
| 312 | |
| 313 | static float wt = 0.0; |
| 314 | wt += 0.1f; |
| 315 | |
| 316 | const int width = logo.width(); |
| 317 | const int dx = width >> 1, dy = dx; // disturbance point |
| 318 | const float W = .3f; |
| 319 | const float v = -4; // wave speed |
| 320 | const int AMP = 5; |
| 321 | |
| 322 | for (int i = 0; i < width; ++i) { |
| 323 | for (int j = 0; j < width; ++j) { |
| 324 | const float s = hypot(x: j - dx, y: i - dy); |
| 325 | const double raw = AMP * sin(x: 2 * M_PI * W * (wt + s / v)); |
| 326 | if (s != 0) |
| 327 | wave[i * width + j] = raw / (0.2 * (s + 2)); |
| 328 | else |
| 329 | wave[i * width + j] = raw; |
| 330 | } |
| 331 | } |
| 332 | } |
| 333 | |