| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the demonstration applications 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 "scene.h" | 
| 52 |  | 
| 53 | #include <QMatrix4x4> | 
| 54 | #include <QRandomGenerator> | 
| 55 | #include <QVector3D> | 
| 56 | #include <qmath.h> | 
| 57 |  | 
| 58 | #include "3rdparty/fbm.h" | 
| 59 |  | 
| 60 | //============================================================================// | 
| 61 | //                                  ColorEdit                                 // | 
| 62 | //============================================================================// | 
| 63 |  | 
| 64 | ColorEdit::ColorEdit(QRgb initialColor, int id) | 
| 65 |     : m_color(initialColor), m_id(id) | 
| 66 | { | 
| 67 |     QHBoxLayout *layout = new QHBoxLayout; | 
| 68 |     setLayout(layout); | 
| 69 |     layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); | 
| 70 |  | 
| 71 |     m_lineEdit = new QLineEdit(QString::number(m_color, base: 16)); | 
| 72 |     layout->addWidget(m_lineEdit); | 
| 73 |  | 
| 74 |     m_button = new QFrame; | 
| 75 |     QPalette palette = m_button->palette(); | 
| 76 |     palette.setColor(acr: QPalette::Window, acolor: QColor(m_color)); | 
| 77 |     m_button->setPalette(palette); | 
| 78 |     m_button->setAutoFillBackground(true); | 
| 79 |     m_button->setMinimumSize(minw: 32, minh: 0); | 
| 80 |     m_button->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Preferred); | 
| 81 |     m_button->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); | 
| 82 |     layout->addWidget(m_button); | 
| 83 |  | 
| 84 |     connect(sender: m_lineEdit, signal: &QLineEdit::editingFinished, receiver: this, slot: &ColorEdit::editDone); | 
| 85 | } | 
| 86 |  | 
| 87 | void ColorEdit::editDone() | 
| 88 | { | 
| 89 |     bool ok; | 
| 90 |     QRgb newColor = m_lineEdit->text().toUInt(ok: &ok, base: 16); | 
| 91 |     if (ok) | 
| 92 |         setColor(newColor); | 
| 93 | } | 
| 94 |  | 
| 95 | void ColorEdit::mousePressEvent(QMouseEvent *event) | 
| 96 | { | 
| 97 |     if (event->button() == Qt::LeftButton) { | 
| 98 |         QColor color(m_color); | 
| 99 |         QColorDialog dialog(color, nullptr); | 
| 100 |         dialog.setOption(option: QColorDialog::ShowAlphaChannel, on: true); | 
| 101 |         dialog.move(ax: 280, ay: 120); | 
| 102 |         if (dialog.exec() == QDialog::Rejected) | 
| 103 |             return; | 
| 104 |         QRgb newColor = dialog.selectedColor().rgba(); | 
| 105 |         if (newColor == m_color) | 
| 106 |             return; | 
| 107 |         setColor(newColor); | 
| 108 |     } | 
| 109 | } | 
| 110 |  | 
| 111 | void ColorEdit::setColor(QRgb color) | 
| 112 | { | 
| 113 |     m_color = color; | 
| 114 |     m_lineEdit->setText(QString::number(m_color, base: 16)); // "Clean up" text | 
| 115 |     QPalette palette = m_button->palette(); | 
| 116 |     palette.setColor(acr: QPalette::Window, acolor: QColor(m_color)); | 
| 117 |     m_button->setPalette(palette); | 
| 118 |     emit colorChanged(color: m_color, id: m_id); | 
| 119 | } | 
| 120 |  | 
| 121 | //============================================================================// | 
| 122 | //                                  FloatEdit                                 // | 
| 123 | //============================================================================// | 
| 124 |  | 
| 125 | FloatEdit::FloatEdit(float initialValue, int id) | 
| 126 |     : m_value(initialValue), m_id(id) | 
| 127 | { | 
| 128 |     QHBoxLayout *layout = new QHBoxLayout; | 
| 129 |     setLayout(layout); | 
| 130 |     layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); | 
| 131 |  | 
| 132 |     m_lineEdit = new QLineEdit(QString::number(m_value)); | 
| 133 |     layout->addWidget(m_lineEdit); | 
| 134 |  | 
| 135 |     connect(sender: m_lineEdit, signal: &QLineEdit::editingFinished, receiver: this, slot: &FloatEdit::editDone); | 
| 136 | } | 
| 137 |  | 
| 138 | void FloatEdit::editDone() | 
| 139 | { | 
| 140 |     bool ok; | 
| 141 |     float newValue = m_lineEdit->text().toFloat(ok: &ok); | 
| 142 |     if (ok) { | 
| 143 |         m_value = newValue; | 
| 144 |         m_lineEdit->setText(QString::number(m_value)); // "Clean up" text | 
| 145 |         emit valueChanged(value: m_value, id: m_id); | 
| 146 |     } | 
| 147 | } | 
| 148 |  | 
| 149 | //============================================================================// | 
| 150 | //                           TwoSidedGraphicsWidget                           // | 
| 151 | //============================================================================// | 
| 152 | void TwoSidedGraphicsWidget::setWidget(int index, QWidget *widget) | 
| 153 | { | 
| 154 |     if (index < 0 || index >= 2) | 
| 155 |     { | 
| 156 |         qWarning(msg: "TwoSidedGraphicsWidget::setWidget: Index out of bounds, index == %d" , index); | 
| 157 |         return; | 
| 158 |     } | 
| 159 |  | 
| 160 |     GraphicsWidget *proxy = new GraphicsWidget; | 
| 161 |     proxy->setWidget(widget); | 
| 162 |  | 
| 163 |     delete m_proxyWidgets[index]; | 
| 164 |     m_proxyWidgets[index] = proxy; | 
| 165 |  | 
| 166 |     proxy->setCacheMode(mode: QGraphicsItem::ItemCoordinateCache); | 
| 167 |     proxy->setZValue(1e30); // Make sure the dialog is drawn on top of all other (OpenGL) items | 
| 168 |  | 
| 169 |     if (index != m_current) | 
| 170 |         proxy->setVisible(false); | 
| 171 |  | 
| 172 |     qobject_cast<QGraphicsScene *>(object: parent())->addItem(item: proxy); | 
| 173 | } | 
| 174 |  | 
| 175 | QWidget *TwoSidedGraphicsWidget::widget(int index) | 
| 176 | { | 
| 177 |     if (index < 0 || index >= 2) | 
| 178 |     { | 
| 179 |         qWarning(msg: "TwoSidedGraphicsWidget::widget: Index out of bounds, index == %d" , index); | 
| 180 |         return nullptr; | 
| 181 |     } | 
| 182 |     return m_proxyWidgets[index]->widget(); | 
| 183 | } | 
| 184 |  | 
| 185 | void TwoSidedGraphicsWidget::flip() | 
| 186 | { | 
| 187 |     m_delta = (m_current == 0 ? 9 : -9); | 
| 188 |     animateFlip(); | 
| 189 | } | 
| 190 |  | 
| 191 | void TwoSidedGraphicsWidget::animateFlip() | 
| 192 | { | 
| 193 |     m_angle += m_delta; | 
| 194 |     if (m_angle == 90) { | 
| 195 |         int old = m_current; | 
| 196 |         m_current ^= 1; | 
| 197 |         m_proxyWidgets[old]->setVisible(false); | 
| 198 |         m_proxyWidgets[m_current]->setVisible(true); | 
| 199 |         m_proxyWidgets[m_current]->setGeometry(m_proxyWidgets[old]->geometry()); | 
| 200 |     } | 
| 201 |  | 
| 202 |     QRectF r = m_proxyWidgets[m_current]->boundingRect(); | 
| 203 |     m_proxyWidgets[m_current]->setTransform(matrix: QTransform() | 
| 204 |         .translate(dx: r.width() / 2, dy: r.height() / 2) | 
| 205 |         .rotate(a: m_angle - 180 * m_current, axis: Qt::YAxis) | 
| 206 |         .translate(dx: -r.width() / 2, dy: -r.height() / 2)); | 
| 207 |  | 
| 208 |     if ((m_current == 0 && m_angle > 0) || (m_current == 1 && m_angle < 180)) | 
| 209 |         QTimer::singleShot(interval: 25, receiver: this, slot: &TwoSidedGraphicsWidget::animateFlip); | 
| 210 | } | 
| 211 |  | 
| 212 | QVariant GraphicsWidget::itemChange(GraphicsItemChange change, const QVariant &value) | 
| 213 | { | 
| 214 |     if (change == ItemPositionChange && scene()) { | 
| 215 |         QRectF rect = boundingRect(); | 
| 216 |         QPointF pos = value.toPointF(); | 
| 217 |         QRectF sceneRect = scene()->sceneRect(); | 
| 218 |         if (pos.x() + rect.left() < sceneRect.left()) | 
| 219 |             pos.setX(sceneRect.left() - rect.left()); | 
| 220 |         else if (pos.x() + rect.right() >= sceneRect.right()) | 
| 221 |             pos.setX(sceneRect.right() - rect.right()); | 
| 222 |         if (pos.y() + rect.top() < sceneRect.top()) | 
| 223 |             pos.setY(sceneRect.top() - rect.top()); | 
| 224 |         else if (pos.y() + rect.bottom() >= sceneRect.bottom()) | 
| 225 |             pos.setY(sceneRect.bottom() - rect.bottom()); | 
| 226 |         return pos; | 
| 227 |     } | 
| 228 |     return QGraphicsProxyWidget::itemChange(change, value); | 
| 229 | } | 
| 230 |  | 
| 231 | void GraphicsWidget::resizeEvent(QGraphicsSceneResizeEvent *event) | 
| 232 | { | 
| 233 |     setCacheMode(mode: QGraphicsItem::NoCache); | 
| 234 |     setCacheMode(mode: QGraphicsItem::ItemCoordinateCache); | 
| 235 |     QGraphicsProxyWidget::resizeEvent(event); | 
| 236 | } | 
| 237 |  | 
| 238 | void GraphicsWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | 
| 239 | { | 
| 240 |     painter->setRenderHint(hint: QPainter::Antialiasing, on: false); | 
| 241 |     QGraphicsProxyWidget::paint(painter, option, widget); | 
| 242 |     //painter->setRenderHint(QPainter::Antialiasing, true); | 
| 243 | } | 
| 244 |  | 
| 245 | //============================================================================// | 
| 246 | //                             RenderOptionsDialog                            // | 
| 247 | //============================================================================// | 
| 248 |  | 
| 249 | RenderOptionsDialog::RenderOptionsDialog() | 
| 250 |     : QDialog(nullptr, Qt::CustomizeWindowHint | Qt::WindowTitleHint) | 
| 251 | { | 
| 252 |     setWindowOpacity(0.75); | 
| 253 |     setWindowTitle(tr(s: "Options (double click to flip)" )); | 
| 254 |     QGridLayout *layout = new QGridLayout; | 
| 255 |     setLayout(layout); | 
| 256 |     layout->setColumnStretch(column: 1, stretch: 1); | 
| 257 |  | 
| 258 |     int row = 0; | 
| 259 |  | 
| 260 |     QCheckBox *check = new QCheckBox(tr(s: "Dynamic cube map" )); | 
| 261 |     check->setCheckState(Qt::Unchecked); | 
| 262 |     // Dynamic cube maps are only enabled when multi-texturing and render to texture are available. | 
| 263 |     check->setEnabled(glActiveTexture && glGenFramebuffersEXT); | 
| 264 |     connect(sender: check, signal: &QCheckBox::stateChanged, receiver: this, slot: &RenderOptionsDialog::dynamicCubemapToggled); | 
| 265 |     layout->addWidget(check, row: 0, column: 0, rowSpan: 1, columnSpan: 2); | 
| 266 |     ++row; | 
| 267 |  | 
| 268 |     // Load all .par files | 
| 269 |     // .par files have a simple syntax for specifying user adjustable uniform variables. | 
| 270 |     const QList<QFileInfo> files = QDir(QStringLiteral(":/res/boxes/" )) | 
| 271 |             .entryInfoList(nameFilters: { QStringLiteral("*.par" ) }, | 
| 272 |                            filters: QDir::Files | QDir::Readable); | 
| 273 |  | 
| 274 |     for (const QFileInfo &fileInfo : files) { | 
| 275 |         QFile file(fileInfo.absoluteFilePath()); | 
| 276 |         if (file.open(flags: QIODevice::ReadOnly)) { | 
| 277 |             while (!file.atEnd()) { | 
| 278 |                 QList<QByteArray> tokens = file.readLine().simplified().split(sep: ' '); | 
| 279 |                 QList<QByteArray>::const_iterator it = tokens.begin(); | 
| 280 |                 if (it == tokens.end()) | 
| 281 |                     continue; | 
| 282 |                 QByteArray type = *it; | 
| 283 |                 if (++it == tokens.end()) | 
| 284 |                     continue; | 
| 285 |                 QByteArray name = *it; | 
| 286 |                 bool singleElement = (tokens.size() == 3); // type, name and one value | 
| 287 |                 char counter[10] = "000000000" ; | 
| 288 |                 int counterPos = 8; // position of last digit | 
| 289 |                 while (++it != tokens.end()) { | 
| 290 |                     m_parameterNames << name; | 
| 291 |                     if (!singleElement) { | 
| 292 |                         m_parameterNames.back() += '['; | 
| 293 |                         m_parameterNames.back() += counter + counterPos; | 
| 294 |                         m_parameterNames.back() += ']'; | 
| 295 |                         int j = 8; // position of last digit | 
| 296 |                         ++counter[j]; | 
| 297 |                         while (j > 0 && counter[j] > '9') { | 
| 298 |                             counter[j] = '0'; | 
| 299 |                             ++counter[--j]; | 
| 300 |                         } | 
| 301 |                         if (j < counterPos) | 
| 302 |                             counterPos = j; | 
| 303 |                     } | 
| 304 |  | 
| 305 |                     if (type == "color" ) { | 
| 306 |                         layout->addWidget(w: new QLabel(m_parameterNames.back())); | 
| 307 |                         bool ok; | 
| 308 |                         ColorEdit *colorEdit = new ColorEdit(it->toUInt(ok: &ok, base: 16), m_parameterNames.size() - 1); | 
| 309 |                         m_parameterEdits << colorEdit; | 
| 310 |                         layout->addWidget(w: colorEdit); | 
| 311 |                         connect(sender: colorEdit, signal: &ColorEdit::colorChanged, receiver: this, slot: &RenderOptionsDialog::setColorParameter); | 
| 312 |                         ++row; | 
| 313 |                     } else if (type == "float" ) { | 
| 314 |                         layout->addWidget(w: new QLabel(m_parameterNames.back())); | 
| 315 |                         bool ok; | 
| 316 |                         FloatEdit *floatEdit = new FloatEdit(it->toFloat(ok: &ok), m_parameterNames.size() - 1); | 
| 317 |                         m_parameterEdits << floatEdit; | 
| 318 |                         layout->addWidget(w: floatEdit); | 
| 319 |                         connect(sender: floatEdit, signal: &FloatEdit::valueChanged, receiver: this, slot: &RenderOptionsDialog::setFloatParameter); | 
| 320 |                         ++row; | 
| 321 |                     } | 
| 322 |                 } | 
| 323 |             } | 
| 324 |             file.close(); | 
| 325 |         } | 
| 326 |     } | 
| 327 |  | 
| 328 |     layout->addWidget(w: new QLabel(tr(s: "Texture:" ))); | 
| 329 |     m_textureCombo = new QComboBox; | 
| 330 |     connect(sender: m_textureCombo, signal: QOverload<int>::of(ptr: &QComboBox::currentIndexChanged), | 
| 331 |             receiver: this, slot: &RenderOptionsDialog::textureChanged); | 
| 332 |     layout->addWidget(w: m_textureCombo); | 
| 333 |     ++row; | 
| 334 |  | 
| 335 |     layout->addWidget(w: new QLabel(tr(s: "Shader:" ))); | 
| 336 |     m_shaderCombo = new QComboBox; | 
| 337 |     connect(sender: m_shaderCombo, signal: QOverload<int>::of(ptr: &QComboBox::currentIndexChanged), | 
| 338 |             receiver: this, slot: &RenderOptionsDialog::shaderChanged); | 
| 339 |     layout->addWidget(w: m_shaderCombo); | 
| 340 |     ++row; | 
| 341 |  | 
| 342 |     layout->setRowStretch(row, stretch: 1); | 
| 343 | } | 
| 344 |  | 
| 345 | int RenderOptionsDialog::addTexture(const QString &name) | 
| 346 | { | 
| 347 |     m_textureCombo->addItem(atext: name); | 
| 348 |     return m_textureCombo->count() - 1; | 
| 349 | } | 
| 350 |  | 
| 351 | int RenderOptionsDialog::addShader(const QString &name) | 
| 352 | { | 
| 353 |     m_shaderCombo->addItem(atext: name); | 
| 354 |     return m_shaderCombo->count() - 1; | 
| 355 | } | 
| 356 |  | 
| 357 | void RenderOptionsDialog::emitParameterChanged() | 
| 358 | { | 
| 359 |     for (ParameterEdit *edit : qAsConst(t&: m_parameterEdits)) | 
| 360 |         edit->emitChange(); | 
| 361 | } | 
| 362 |  | 
| 363 | void RenderOptionsDialog::setColorParameter(QRgb color, int id) | 
| 364 | { | 
| 365 |     emit colorParameterChanged(m_parameterNames[id], color); | 
| 366 | } | 
| 367 |  | 
| 368 | void RenderOptionsDialog::setFloatParameter(float value, int id) | 
| 369 | { | 
| 370 |     emit floatParameterChanged(m_parameterNames[id], value); | 
| 371 | } | 
| 372 |  | 
| 373 | void RenderOptionsDialog::mouseDoubleClickEvent(QMouseEvent *event) | 
| 374 | { | 
| 375 |     if (event->button() == Qt::LeftButton) | 
| 376 |         emit doubleClicked(); | 
| 377 | } | 
| 378 |  | 
| 379 | //============================================================================// | 
| 380 | //                                 ItemDialog                                 // | 
| 381 | //============================================================================// | 
| 382 |  | 
| 383 | ItemDialog::ItemDialog() | 
| 384 |     : QDialog(nullptr, Qt::CustomizeWindowHint | Qt::WindowTitleHint) | 
| 385 | { | 
| 386 |     setWindowTitle(tr(s: "Items (double click to flip)" )); | 
| 387 |     setWindowOpacity(0.75); | 
| 388 |     resize(w: 160, h: 100); | 
| 389 |  | 
| 390 |     QVBoxLayout *layout = new QVBoxLayout; | 
| 391 |     setLayout(layout); | 
| 392 |     QPushButton *button; | 
| 393 |  | 
| 394 |     button = new QPushButton(tr(s: "Add Qt box" )); | 
| 395 |     layout->addWidget(button); | 
| 396 |     connect(sender: button, signal: &QAbstractButton::clicked, receiver: this, slot: &ItemDialog::triggerNewQtBox); | 
| 397 |  | 
| 398 |     button = new QPushButton(tr(s: "Add circle" )); | 
| 399 |     layout->addWidget(button); | 
| 400 |     connect(sender: button, signal: &QAbstractButton::clicked, receiver: this, slot: &ItemDialog::triggerNewCircleItem); | 
| 401 |  | 
| 402 |     button = new QPushButton(tr(s: "Add square" )); | 
| 403 |     layout->addWidget(button); | 
| 404 |     connect(sender: button, signal: &QAbstractButton::clicked, receiver: this, slot: &ItemDialog::triggerNewSquareItem); | 
| 405 |  | 
| 406 |     layout->addStretch(stretch: 1); | 
| 407 | } | 
| 408 |  | 
| 409 | void ItemDialog::triggerNewQtBox() | 
| 410 | { | 
| 411 |     emit newItemTriggered(type: QtBoxItem); | 
| 412 | } | 
| 413 |  | 
| 414 | void ItemDialog::triggerNewCircleItem() | 
| 415 | { | 
| 416 |     emit newItemTriggered(type: CircleItem); | 
| 417 | } | 
| 418 |  | 
| 419 | void ItemDialog::triggerNewSquareItem() | 
| 420 | { | 
| 421 |     emit newItemTriggered(type: SquareItem); | 
| 422 | } | 
| 423 |  | 
| 424 | void ItemDialog::mouseDoubleClickEvent(QMouseEvent *event) | 
| 425 | { | 
| 426 |     if (event->button() == Qt::LeftButton) | 
| 427 |         emit doubleClicked(); | 
| 428 | } | 
| 429 |  | 
| 430 | //============================================================================// | 
| 431 | //                                    Scene                                   // | 
| 432 | //============================================================================// | 
| 433 |  | 
| 434 | const static char environmentShaderText[] = | 
| 435 |     "uniform samplerCube env;"  | 
| 436 |     "void main() {"  | 
| 437 |         "gl_FragColor = textureCube(env, gl_TexCoord[1].xyz);"  | 
| 438 |     "}" ; | 
| 439 |  | 
| 440 | Scene::Scene(int width, int height, int maxTextureSize) | 
| 441 |     : m_distExp(600) | 
| 442 |     , m_frame(0) | 
| 443 |     , m_maxTextureSize(maxTextureSize) | 
| 444 |     , m_currentShader(0) | 
| 445 |     , m_currentTexture(0) | 
| 446 |     , m_dynamicCubemap(false) | 
| 447 |     , m_updateAllCubemaps(true) | 
| 448 |     , m_box(nullptr) | 
| 449 |     , m_vertexShader(nullptr) | 
| 450 |     , m_environmentShader(nullptr) | 
| 451 |     , m_environmentProgram(nullptr) | 
| 452 | { | 
| 453 |     setSceneRect(x: 0, y: 0, w: width, h: height); | 
| 454 |  | 
| 455 |     m_trackBalls[0] = TrackBall(0.05f, QVector3D(0, 1, 0), TrackBall::Sphere); | 
| 456 |     m_trackBalls[1] = TrackBall(0.005f, QVector3D(0, 0, 1), TrackBall::Sphere); | 
| 457 |     m_trackBalls[2] = TrackBall(0.0f, QVector3D(0, 1, 0), TrackBall::Plane); | 
| 458 |  | 
| 459 |     m_renderOptions = new RenderOptionsDialog; | 
| 460 |     m_renderOptions->move(ax: 20, ay: 120); | 
| 461 |     m_renderOptions->resize(m_renderOptions->sizeHint()); | 
| 462 |  | 
| 463 |     connect(sender: m_renderOptions, signal: &RenderOptionsDialog::dynamicCubemapToggled, receiver: this, slot: &Scene::toggleDynamicCubemap); | 
| 464 |     connect(sender: m_renderOptions, signal: &RenderOptionsDialog::colorParameterChanged, receiver: this, slot: &Scene::setColorParameter); | 
| 465 |     connect(sender: m_renderOptions, signal: &RenderOptionsDialog::floatParameterChanged, receiver: this, slot: &Scene::setFloatParameter); | 
| 466 |     connect(sender: m_renderOptions, signal: &RenderOptionsDialog::textureChanged, receiver: this, slot: &Scene::setTexture); | 
| 467 |     connect(sender: m_renderOptions, signal: &RenderOptionsDialog::shaderChanged, receiver: this, slot: &Scene::setShader); | 
| 468 |  | 
| 469 |     m_itemDialog = new ItemDialog; | 
| 470 |     connect(sender: m_itemDialog, signal: &ItemDialog::newItemTriggered, receiver: this, slot: &Scene::newItem); | 
| 471 |  | 
| 472 |     TwoSidedGraphicsWidget *twoSided = new TwoSidedGraphicsWidget(this); | 
| 473 |     twoSided->setWidget(index: 0, widget: m_renderOptions); | 
| 474 |     twoSided->setWidget(index: 1, widget: m_itemDialog); | 
| 475 |  | 
| 476 |     connect(sender: m_renderOptions, signal: &RenderOptionsDialog::doubleClicked, receiver: twoSided, slot: &TwoSidedGraphicsWidget::flip); | 
| 477 |     connect(sender: m_itemDialog, signal: &ItemDialog::doubleClicked, receiver: twoSided, slot: &TwoSidedGraphicsWidget::flip); | 
| 478 |  | 
| 479 |     addItem(item: new QtBox(64, width - 64, height - 64)); | 
| 480 |     addItem(item: new QtBox(64, width - 64, 64)); | 
| 481 |     addItem(item: new QtBox(64, 64, height - 64)); | 
| 482 |     addItem(item: new QtBox(64, 64, 64)); | 
| 483 |  | 
| 484 |     initGL(); | 
| 485 |  | 
| 486 |     m_timer = new QTimer(this); | 
| 487 |     m_timer->setInterval(20); | 
| 488 |     connect(sender: m_timer, signal: &QTimer::timeout, context: this, slot: [this](){ update(); }); | 
| 489 |     m_timer->start(); | 
| 490 | } | 
| 491 |  | 
| 492 | Scene::~Scene() | 
| 493 | { | 
| 494 |     delete m_box; | 
| 495 |     qDeleteAll(c: m_textures); | 
| 496 |     delete m_mainCubemap; | 
| 497 |     qDeleteAll(c: m_programs); | 
| 498 |     delete m_vertexShader; | 
| 499 |     qDeleteAll(c: m_fragmentShaders); | 
| 500 |     qDeleteAll(c: m_cubemaps); | 
| 501 |     delete m_environmentShader; | 
| 502 |     delete m_environmentProgram; | 
| 503 | } | 
| 504 |  | 
| 505 | void Scene::initGL() | 
| 506 | { | 
| 507 |     m_box = new GLRoundedBox(0.25f, 1.0f, 10); | 
| 508 |  | 
| 509 |     m_vertexShader = new QGLShader(QGLShader::Vertex); | 
| 510 |     m_vertexShader->compileSourceFile(fileName: QLatin1String(":/res/boxes/basic.vsh" )); | 
| 511 |  | 
| 512 |     QStringList list; | 
| 513 |     list << ":/res/boxes/cubemap_posx.jpg"  << ":/res/boxes/cubemap_negx.jpg"  << ":/res/boxes/cubemap_posy.jpg"  | 
| 514 |          << ":/res/boxes/cubemap_negy.jpg"  << ":/res/boxes/cubemap_posz.jpg"  << ":/res/boxes/cubemap_negz.jpg" ; | 
| 515 |     m_environment = new GLTextureCube(list, qMin(a: 1024, b: m_maxTextureSize)); | 
| 516 |     m_environmentShader = new QGLShader(QGLShader::Fragment); | 
| 517 |     m_environmentShader->compileSourceCode(source: environmentShaderText); | 
| 518 |     m_environmentProgram = new QGLShaderProgram; | 
| 519 |     m_environmentProgram->addShader(shader: m_vertexShader); | 
| 520 |     m_environmentProgram->addShader(shader: m_environmentShader); | 
| 521 |     m_environmentProgram->link(); | 
| 522 |  | 
| 523 |     const int NOISE_SIZE = 128; // for a different size, B and BM in fbm.c must also be changed | 
| 524 |     m_noise = new GLTexture3D(NOISE_SIZE, NOISE_SIZE, NOISE_SIZE); | 
| 525 |     QVector<QRgb> data(NOISE_SIZE * NOISE_SIZE * NOISE_SIZE, QRgb(0)); | 
| 526 |     QRgb *p = data.data(); | 
| 527 |     float pos[3]; | 
| 528 |     for (int k = 0; k < NOISE_SIZE; ++k) { | 
| 529 |         pos[2] = k * (0x20 / (float)NOISE_SIZE); | 
| 530 |         for (int j = 0; j < NOISE_SIZE; ++j) { | 
| 531 |             for (int i = 0; i < NOISE_SIZE; ++i) { | 
| 532 |                 for (int byte = 0; byte < 4; ++byte) { | 
| 533 |                     pos[0] = (i + (byte & 1) * 16) * (0x20 / (float)NOISE_SIZE); | 
| 534 |                     pos[1] = (j + (byte & 2) * 8) * (0x20 / (float)NOISE_SIZE); | 
| 535 |                     *p |= (int)(128.0f * (noise3(vec: pos) + 1.0f)) << (byte * 8); | 
| 536 |                 } | 
| 537 |                 ++p; | 
| 538 |             } | 
| 539 |         } | 
| 540 |     } | 
| 541 |     m_noise->load(width: NOISE_SIZE, height: NOISE_SIZE, depth: NOISE_SIZE, data: data.data()); | 
| 542 |  | 
| 543 |     m_mainCubemap = new GLRenderTargetCube(512); | 
| 544 |  | 
| 545 |     QList<QFileInfo> files; | 
| 546 |  | 
| 547 |     // Load all .png files as textures | 
| 548 |     m_currentTexture = 0; | 
| 549 |     files = QDir(":/res/boxes/" ).entryInfoList(nameFilters: { QStringLiteral("*.png" ) }, filters: QDir::Files | QDir::Readable); | 
| 550 |  | 
| 551 |     for (const QFileInfo &file : qAsConst(t&: files)) { | 
| 552 |         GLTexture *texture = new GLTexture2D(file.absoluteFilePath(), qMin(a: 256, b: m_maxTextureSize), qMin(a: 256, b: m_maxTextureSize)); | 
| 553 |         if (texture->failed()) { | 
| 554 |             delete texture; | 
| 555 |             continue; | 
| 556 |         } | 
| 557 |         m_textures << texture; | 
| 558 |         m_renderOptions->addTexture(name: file.baseName()); | 
| 559 |     } | 
| 560 |  | 
| 561 |     if (m_textures.size() == 0) | 
| 562 |         m_textures << new GLTexture2D(qMin(a: 64, b: m_maxTextureSize), qMin(a: 64, b: m_maxTextureSize)); | 
| 563 |  | 
| 564 |     // Load all .fsh files as fragment shaders | 
| 565 |     m_currentShader = 0; | 
| 566 |     files = QDir(":/res/boxes/" ).entryInfoList(nameFilters: { QStringLiteral("*.fsh" ) }, filters: QDir::Files | QDir::Readable); | 
| 567 |     for (const QFileInfo &file : qAsConst(t&: files)) { | 
| 568 |         QGLShaderProgram *program = new QGLShaderProgram; | 
| 569 |         QGLShader* shader = new QGLShader(QGLShader::Fragment); | 
| 570 |         shader->compileSourceFile(fileName: file.absoluteFilePath()); | 
| 571 |         // The program does not take ownership over the shaders, so store them in a vector so they can be deleted afterwards. | 
| 572 |         program->addShader(shader: m_vertexShader); | 
| 573 |         program->addShader(shader); | 
| 574 |         if (!program->link()) { | 
| 575 |             qWarning(msg: "Failed to compile and link shader program" ); | 
| 576 |             qWarning(msg: "Vertex shader log:" ); | 
| 577 |             qWarning() << m_vertexShader->log(); | 
| 578 |             qWarning() << "Fragment shader log ( file ="  << file.absoluteFilePath() << "):" ; | 
| 579 |             qWarning() << shader->log(); | 
| 580 |             qWarning(msg: "Shader program log:" ); | 
| 581 |             qWarning() << program->log(); | 
| 582 |  | 
| 583 |             delete shader; | 
| 584 |             delete program; | 
| 585 |             continue; | 
| 586 |         } | 
| 587 |  | 
| 588 |         m_fragmentShaders << shader; | 
| 589 |         m_programs << program; | 
| 590 |         m_renderOptions->addShader(name: file.baseName()); | 
| 591 |  | 
| 592 |         program->bind(); | 
| 593 |         m_cubemaps << ((program->uniformLocation(name: "env" ) != -1) ? new GLRenderTargetCube(qMin(a: 256, b: m_maxTextureSize)) : nullptr); | 
| 594 |         program->release(); | 
| 595 |     } | 
| 596 |  | 
| 597 |     if (m_programs.size() == 0) | 
| 598 |         m_programs << new QGLShaderProgram; | 
| 599 |  | 
| 600 |     m_renderOptions->emitParameterChanged(); | 
| 601 | } | 
| 602 |  | 
| 603 | static void loadMatrix(const QMatrix4x4 &m) | 
| 604 | { | 
| 605 |     // static to prevent glLoadMatrixf to fail on certain drivers | 
| 606 |     static GLfloat mat[16]; | 
| 607 |     const float *data = m.constData(); | 
| 608 |     for (int index = 0; index < 16; ++index) | 
| 609 |         mat[index] = data[index]; | 
| 610 |     glLoadMatrixf(m: mat); | 
| 611 | } | 
| 612 |  | 
| 613 | // If one of the boxes should not be rendered, set excludeBox to its index. | 
| 614 | // If the main box should not be rendered, set excludeBox to -1. | 
| 615 | void Scene::renderBoxes(const QMatrix4x4 &view, int excludeBox) | 
| 616 | { | 
| 617 |     QMatrix4x4 invView = view.inverted(); | 
| 618 |  | 
| 619 |     // If multi-texturing is supported, use three saplers. | 
| 620 |     if (glActiveTexture) { | 
| 621 |         glActiveTexture(GL_TEXTURE0); | 
| 622 |         m_textures[m_currentTexture]->bind(); | 
| 623 |         glActiveTexture(GL_TEXTURE2); | 
| 624 |         m_noise->bind(); | 
| 625 |         glActiveTexture(GL_TEXTURE1); | 
| 626 |     } else { | 
| 627 |         m_textures[m_currentTexture]->bind(); | 
| 628 |     } | 
| 629 |  | 
| 630 |     glDisable(GL_LIGHTING); | 
| 631 |     glDisable(GL_CULL_FACE); | 
| 632 |  | 
| 633 |     QMatrix4x4 viewRotation(view); | 
| 634 |     viewRotation(3, 0) = viewRotation(3, 1) = viewRotation(3, 2) = 0.0f; | 
| 635 |     viewRotation(0, 3) = viewRotation(1, 3) = viewRotation(2, 3) = 0.0f; | 
| 636 |     viewRotation(3, 3) = 1.0f; | 
| 637 |     loadMatrix(m: viewRotation); | 
| 638 |     glScalef(x: 20.0f, y: 20.0f, z: 20.0f); | 
| 639 |  | 
| 640 |     // Don't render the environment if the environment texture can't be set for the correct sampler. | 
| 641 |     if (glActiveTexture) { | 
| 642 |         m_environment->bind(); | 
| 643 |         m_environmentProgram->bind(); | 
| 644 |         m_environmentProgram->setUniformValue(name: "tex" , value: GLint(0)); | 
| 645 |         m_environmentProgram->setUniformValue(name: "env" , value: GLint(1)); | 
| 646 |         m_environmentProgram->setUniformValue(name: "noise" , value: GLint(2)); | 
| 647 |         m_box->draw(); | 
| 648 |         m_environmentProgram->release(); | 
| 649 |         m_environment->unbind(); | 
| 650 |     } | 
| 651 |  | 
| 652 |     loadMatrix(m: view); | 
| 653 |  | 
| 654 |     glEnable(GL_CULL_FACE); | 
| 655 |     glEnable(GL_LIGHTING); | 
| 656 |  | 
| 657 |     for (int i = 0; i < m_programs.size(); ++i) { | 
| 658 |         if (i == excludeBox) | 
| 659 |             continue; | 
| 660 |  | 
| 661 |         glPushMatrix(); | 
| 662 |         QMatrix4x4 m; | 
| 663 |         m.rotate(quaternion: m_trackBalls[1].rotation()); | 
| 664 |         glMultMatrixf(m: m.constData()); | 
| 665 |  | 
| 666 |         glRotatef(angle: 360.0f * i / m_programs.size(), x: 0.0f, y: 0.0f, z: 1.0f); | 
| 667 |         glTranslatef(x: 2.0f, y: 0.0f, z: 0.0f); | 
| 668 |         glScalef(x: 0.3f, y: 0.6f, z: 0.6f); | 
| 669 |  | 
| 670 |         if (glActiveTexture) { | 
| 671 |             if (m_dynamicCubemap && m_cubemaps[i]) | 
| 672 |                 m_cubemaps[i]->bind(); | 
| 673 |             else | 
| 674 |                 m_environment->bind(); | 
| 675 |         } | 
| 676 |         m_programs[i]->bind(); | 
| 677 |         m_programs[i]->setUniformValue(name: "tex" , value: GLint(0)); | 
| 678 |         m_programs[i]->setUniformValue(name: "env" , value: GLint(1)); | 
| 679 |         m_programs[i]->setUniformValue(name: "noise" , value: GLint(2)); | 
| 680 |         m_programs[i]->setUniformValue(name: "view" , value: view); | 
| 681 |         m_programs[i]->setUniformValue(name: "invView" , value: invView); | 
| 682 |         m_box->draw(); | 
| 683 |         m_programs[i]->release(); | 
| 684 |  | 
| 685 |         if (glActiveTexture) { | 
| 686 |             if (m_dynamicCubemap && m_cubemaps[i]) | 
| 687 |                 m_cubemaps[i]->unbind(); | 
| 688 |             else | 
| 689 |                 m_environment->unbind(); | 
| 690 |         } | 
| 691 |         glPopMatrix(); | 
| 692 |     } | 
| 693 |  | 
| 694 |     if (-1 != excludeBox) { | 
| 695 |         QMatrix4x4 m; | 
| 696 |         m.rotate(quaternion: m_trackBalls[0].rotation()); | 
| 697 |         glMultMatrixf(m: m.constData()); | 
| 698 |  | 
| 699 |         if (glActiveTexture) { | 
| 700 |             if (m_dynamicCubemap) | 
| 701 |                 m_mainCubemap->bind(); | 
| 702 |             else | 
| 703 |                 m_environment->bind(); | 
| 704 |         } | 
| 705 |  | 
| 706 |         m_programs[m_currentShader]->bind(); | 
| 707 |         m_programs[m_currentShader]->setUniformValue(name: "tex" , value: GLint(0)); | 
| 708 |         m_programs[m_currentShader]->setUniformValue(name: "env" , value: GLint(1)); | 
| 709 |         m_programs[m_currentShader]->setUniformValue(name: "noise" , value: GLint(2)); | 
| 710 |         m_programs[m_currentShader]->setUniformValue(name: "view" , value: view); | 
| 711 |         m_programs[m_currentShader]->setUniformValue(name: "invView" , value: invView); | 
| 712 |         m_box->draw(); | 
| 713 |         m_programs[m_currentShader]->release(); | 
| 714 |  | 
| 715 |         if (glActiveTexture) { | 
| 716 |             if (m_dynamicCubemap) | 
| 717 |                 m_mainCubemap->unbind(); | 
| 718 |             else | 
| 719 |                 m_environment->unbind(); | 
| 720 |         } | 
| 721 |     } | 
| 722 |  | 
| 723 |     if (glActiveTexture) { | 
| 724 |         glActiveTexture(GL_TEXTURE2); | 
| 725 |         m_noise->unbind(); | 
| 726 |         glActiveTexture(GL_TEXTURE0); | 
| 727 |     } | 
| 728 |     m_textures[m_currentTexture]->unbind(); | 
| 729 | } | 
| 730 |  | 
| 731 | void Scene::setStates() | 
| 732 | { | 
| 733 |     //glClearColor(0.25f, 0.25f, 0.5f, 1.0f); | 
| 734 |  | 
| 735 |     glEnable(GL_DEPTH_TEST); | 
| 736 |     glEnable(GL_CULL_FACE); | 
| 737 |     glEnable(GL_LIGHTING); | 
| 738 |     //glEnable(GL_COLOR_MATERIAL); | 
| 739 |     glEnable(GL_TEXTURE_2D); | 
| 740 |     glEnable(GL_NORMALIZE); | 
| 741 |  | 
| 742 |     glMatrixMode(GL_PROJECTION); | 
| 743 |     glPushMatrix(); | 
| 744 |     glLoadIdentity(); | 
| 745 |  | 
| 746 |     glMatrixMode(GL_MODELVIEW); | 
| 747 |     glPushMatrix(); | 
| 748 |     glLoadIdentity(); | 
| 749 |  | 
| 750 |     setLights(); | 
| 751 |  | 
| 752 |     float materialSpecular[] = {0.5f, 0.5f, 0.5f, 1.0f}; | 
| 753 |     glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, params: materialSpecular); | 
| 754 |     glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, param: 32.0f); | 
| 755 | } | 
| 756 |  | 
| 757 | void Scene::setLights() | 
| 758 | { | 
| 759 |     glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); | 
| 760 |     //float lightColour[] = {1.0f, 1.0f, 1.0f, 1.0f}; | 
| 761 |     float lightDir[] = {0.0f, 0.0f, 1.0f, 0.0f}; | 
| 762 |     //glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColour); | 
| 763 |     //glLightfv(GL_LIGHT0, GL_SPECULAR, lightColour); | 
| 764 |     glLightfv(GL_LIGHT0, GL_POSITION, params: lightDir); | 
| 765 |     glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, param: 1.0f); | 
| 766 |     glEnable(GL_LIGHT0); | 
| 767 | } | 
| 768 |  | 
| 769 | void Scene::defaultStates() | 
| 770 | { | 
| 771 |     //glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | 
| 772 |  | 
| 773 |     glDisable(GL_DEPTH_TEST); | 
| 774 |     glDisable(GL_CULL_FACE); | 
| 775 |     glDisable(GL_LIGHTING); | 
| 776 |     //glDisable(GL_COLOR_MATERIAL); | 
| 777 |     glDisable(GL_TEXTURE_2D); | 
| 778 |     glDisable(GL_LIGHT0); | 
| 779 |     glDisable(GL_NORMALIZE); | 
| 780 |  | 
| 781 |     glMatrixMode(GL_MODELVIEW); | 
| 782 |     glPopMatrix(); | 
| 783 |  | 
| 784 |     glMatrixMode(GL_PROJECTION); | 
| 785 |     glPopMatrix(); | 
| 786 |  | 
| 787 |     glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, param: 0.0f); | 
| 788 |     float defaultMaterialSpecular[] = {0.0f, 0.0f, 0.0f, 1.0f}; | 
| 789 |     glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, params: defaultMaterialSpecular); | 
| 790 |     glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, param: 0.0f); | 
| 791 | } | 
| 792 |  | 
| 793 | void Scene::renderCubemaps() | 
| 794 | { | 
| 795 |     // To speed things up, only update the cubemaps for the small cubes every N frames. | 
| 796 |     const int N = (m_updateAllCubemaps ? 1 : 3); | 
| 797 |  | 
| 798 |     QMatrix4x4 mat; | 
| 799 |     GLRenderTargetCube::getProjectionMatrix(mat, nearZ: 0.1f, farZ: 100.0f); | 
| 800 |  | 
| 801 |     glMatrixMode(GL_PROJECTION); | 
| 802 |     glPushMatrix(); | 
| 803 |     loadMatrix(m: mat); | 
| 804 |  | 
| 805 |     glMatrixMode(GL_MODELVIEW); | 
| 806 |     glPushMatrix(); | 
| 807 |  | 
| 808 |     QVector3D center; | 
| 809 |  | 
| 810 |     const float eachAngle = 2 * M_PI / m_cubemaps.size(); | 
| 811 |     for (int i = m_frame % N; i < m_cubemaps.size(); i += N) { | 
| 812 |         if (0 == m_cubemaps[i]) | 
| 813 |             continue; | 
| 814 |  | 
| 815 |         float angle = i * eachAngle; | 
| 816 |  | 
| 817 |         center = m_trackBalls[1].rotation().rotatedVector(vector: QVector3D(std::cos(x: angle), std::sin(x: angle), 0.0f)); | 
| 818 |  | 
| 819 |         for (int face = 0; face < 6; ++face) { | 
| 820 |             m_cubemaps[i]->begin(face); | 
| 821 |  | 
| 822 |             GLRenderTargetCube::getViewMatrix(mat, face); | 
| 823 |             QVector4D v = QVector4D(-center.x(), -center.y(), -center.z(), 1.0); | 
| 824 |             mat.setColumn(index: 3, value: mat * v); | 
| 825 |  | 
| 826 |             glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | 
| 827 |             renderBoxes(view: mat, excludeBox: i); | 
| 828 |  | 
| 829 |             m_cubemaps[i]->end(); | 
| 830 |         } | 
| 831 |     } | 
| 832 |  | 
| 833 |     for (int face = 0; face < 6; ++face) { | 
| 834 |         m_mainCubemap->begin(face); | 
| 835 |         GLRenderTargetCube::getViewMatrix(mat, face); | 
| 836 |  | 
| 837 |         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | 
| 838 |         renderBoxes(view: mat, excludeBox: -1); | 
| 839 |  | 
| 840 |         m_mainCubemap->end(); | 
| 841 |     } | 
| 842 |  | 
| 843 |     glPopMatrix(); | 
| 844 |  | 
| 845 |     glMatrixMode(GL_PROJECTION); | 
| 846 |     glPopMatrix(); | 
| 847 |  | 
| 848 |     m_updateAllCubemaps = false; | 
| 849 | } | 
| 850 |  | 
| 851 | void Scene::drawBackground(QPainter *painter, const QRectF &) | 
| 852 | { | 
| 853 |     float width = float(painter->device()->width()); | 
| 854 |     float height = float(painter->device()->height()); | 
| 855 |  | 
| 856 |     painter->beginNativePainting(); | 
| 857 |     setStates(); | 
| 858 |  | 
| 859 |     if (m_dynamicCubemap) | 
| 860 |         renderCubemaps(); | 
| 861 |  | 
| 862 |     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | 
| 863 |  | 
| 864 |     glMatrixMode(GL_PROJECTION); | 
| 865 |     qgluPerspective(fovy: 60.0, aspect: width / height, zNear: 0.01, zFar: 15.0); | 
| 866 |  | 
| 867 |     glMatrixMode(GL_MODELVIEW); | 
| 868 |  | 
| 869 |     QMatrix4x4 view; | 
| 870 |     view.rotate(quaternion: m_trackBalls[2].rotation()); | 
| 871 |     view(2, 3) -= 2.0f * std::exp(x: m_distExp / 1200.0f); | 
| 872 |     renderBoxes(view); | 
| 873 |  | 
| 874 |     defaultStates(); | 
| 875 |     ++m_frame; | 
| 876 |  | 
| 877 |     painter->endNativePainting(); | 
| 878 | } | 
| 879 |  | 
| 880 | QPointF Scene::pixelPosToViewPos(const QPointF& p) | 
| 881 | { | 
| 882 |     return QPointF(2.0 * float(p.x()) / width() - 1.0, | 
| 883 |                    1.0 - 2.0 * float(p.y()) / height()); | 
| 884 | } | 
| 885 |  | 
| 886 | void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | 
| 887 | { | 
| 888 |     QGraphicsScene::mouseMoveEvent(event); | 
| 889 |     if (event->isAccepted()) | 
| 890 |         return; | 
| 891 |  | 
| 892 |     if (event->buttons() & Qt::LeftButton) { | 
| 893 |         m_trackBalls[0].move(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 894 |         event->accept(); | 
| 895 |     } else { | 
| 896 |         m_trackBalls[0].release(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 897 |     } | 
| 898 |  | 
| 899 |     if (event->buttons() & Qt::RightButton) { | 
| 900 |         m_trackBalls[1].move(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 901 |         event->accept(); | 
| 902 |     } else { | 
| 903 |         m_trackBalls[1].release(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 904 |     } | 
| 905 |  | 
| 906 |     if (event->buttons() & Qt::MidButton) { | 
| 907 |         m_trackBalls[2].move(p: pixelPosToViewPos(p: event->scenePos()), transformation: QQuaternion()); | 
| 908 |         event->accept(); | 
| 909 |     } else { | 
| 910 |         m_trackBalls[2].release(p: pixelPosToViewPos(p: event->scenePos()), transformation: QQuaternion()); | 
| 911 |     } | 
| 912 | } | 
| 913 |  | 
| 914 | void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) | 
| 915 | { | 
| 916 |     QGraphicsScene::mousePressEvent(event); | 
| 917 |     if (event->isAccepted()) | 
| 918 |         return; | 
| 919 |  | 
| 920 |     if (event->buttons() & Qt::LeftButton) { | 
| 921 |         m_trackBalls[0].push(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 922 |         event->accept(); | 
| 923 |     } | 
| 924 |  | 
| 925 |     if (event->buttons() & Qt::RightButton) { | 
| 926 |         m_trackBalls[1].push(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 927 |         event->accept(); | 
| 928 |     } | 
| 929 |  | 
| 930 |     if (event->buttons() & Qt::MidButton) { | 
| 931 |         m_trackBalls[2].push(p: pixelPosToViewPos(p: event->scenePos()), transformation: QQuaternion()); | 
| 932 |         event->accept(); | 
| 933 |     } | 
| 934 | } | 
| 935 |  | 
| 936 | void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) | 
| 937 | { | 
| 938 |     QGraphicsScene::mouseReleaseEvent(event); | 
| 939 |     if (event->isAccepted()) | 
| 940 |         return; | 
| 941 |  | 
| 942 |     if (event->button() == Qt::LeftButton) { | 
| 943 |         m_trackBalls[0].release(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 944 |         event->accept(); | 
| 945 |     } | 
| 946 |  | 
| 947 |     if (event->button() == Qt::RightButton) { | 
| 948 |         m_trackBalls[1].release(p: pixelPosToViewPos(p: event->scenePos()), transformation: m_trackBalls[2].rotation().conjugated()); | 
| 949 |         event->accept(); | 
| 950 |     } | 
| 951 |  | 
| 952 |     if (event->button() == Qt::MidButton) { | 
| 953 |         m_trackBalls[2].release(p: pixelPosToViewPos(p: event->scenePos()), transformation: QQuaternion()); | 
| 954 |         event->accept(); | 
| 955 |     } | 
| 956 | } | 
| 957 |  | 
| 958 | void Scene::wheelEvent(QGraphicsSceneWheelEvent * event) | 
| 959 | { | 
| 960 |     QGraphicsScene::wheelEvent(event); | 
| 961 |     if (!event->isAccepted()) { | 
| 962 |         m_distExp += event->delta(); | 
| 963 |         if (m_distExp < -8 * 120) | 
| 964 |             m_distExp = -8 * 120; | 
| 965 |         if (m_distExp > 10 * 120) | 
| 966 |             m_distExp = 10 * 120; | 
| 967 |         event->accept(); | 
| 968 |     } | 
| 969 | } | 
| 970 |  | 
| 971 | void Scene::setShader(int index) | 
| 972 | { | 
| 973 |     if (index >= 0 && index < m_fragmentShaders.size()) | 
| 974 |         m_currentShader = index; | 
| 975 | } | 
| 976 |  | 
| 977 | void Scene::setTexture(int index) | 
| 978 | { | 
| 979 |     if (index >= 0 && index < m_textures.size()) | 
| 980 |         m_currentTexture = index; | 
| 981 | } | 
| 982 |  | 
| 983 | void Scene::toggleDynamicCubemap(int state) | 
| 984 | { | 
| 985 |     if ((m_dynamicCubemap = (state == Qt::Checked))) | 
| 986 |         m_updateAllCubemaps = true; | 
| 987 | } | 
| 988 |  | 
| 989 | void Scene::setColorParameter(const QString &name, QRgb color) | 
| 990 | { | 
| 991 |     // set the color in all programs | 
| 992 |     for (QGLShaderProgram *program : qAsConst(t&: m_programs)) { | 
| 993 |         program->bind(); | 
| 994 |         program->setUniformValue(location: program->uniformLocation(name), color: QColor(color)); | 
| 995 |         program->release(); | 
| 996 |     } | 
| 997 | } | 
| 998 |  | 
| 999 | void Scene::setFloatParameter(const QString &name, float value) | 
| 1000 | { | 
| 1001 |     // set the color in all programs | 
| 1002 |     for (QGLShaderProgram *program : qAsConst(t&: m_programs)) { | 
| 1003 |         program->bind(); | 
| 1004 |         program->setUniformValue(location: program->uniformLocation(name), value); | 
| 1005 |         program->release(); | 
| 1006 |     } | 
| 1007 | } | 
| 1008 |  | 
| 1009 | void Scene::newItem(ItemDialog::ItemType type) | 
| 1010 | { | 
| 1011 |     QSize size = sceneRect().size().toSize(); | 
| 1012 |     switch (type) { | 
| 1013 |     case ItemDialog::QtBoxItem: | 
| 1014 |         addItem(item: new QtBox(64, QRandomGenerator::global()->bounded(highest: size.width() - 64) + 32, | 
| 1015 |                           QRandomGenerator::global()->bounded(highest: size.height() - 64) + 32)); | 
| 1016 |         break; | 
| 1017 |     case ItemDialog::CircleItem: | 
| 1018 |         addItem(item: new CircleItem(64, QRandomGenerator::global()->bounded(highest: size.width() - 64) + 32, | 
| 1019 |                                QRandomGenerator::global()->bounded(highest: size.height() - 64) + 32)); | 
| 1020 |         break; | 
| 1021 |     case ItemDialog::SquareItem: | 
| 1022 |         addItem(item: new SquareItem(64, QRandomGenerator::global()->bounded(highest: size.width() - 64) + 32, | 
| 1023 |                                QRandomGenerator::global()->bounded(highest: size.height() - 64) + 32)); | 
| 1024 |         break; | 
| 1025 |     default: | 
| 1026 |         break; | 
| 1027 |     } | 
| 1028 | } | 
| 1029 |  |