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
64ColorEdit::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
87void 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
95void 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
111void 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
125FloatEdit::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
138void 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//============================================================================//
152void 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
175QWidget *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
185void TwoSidedGraphicsWidget::flip()
186{
187 m_delta = (m_current == 0 ? 9 : -9);
188 animateFlip();
189}
190
191void 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
212QVariant 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
231void GraphicsWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
232{
233 setCacheMode(mode: QGraphicsItem::NoCache);
234 setCacheMode(mode: QGraphicsItem::ItemCoordinateCache);
235 QGraphicsProxyWidget::resizeEvent(event);
236}
237
238void 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
249RenderOptionsDialog::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
345int RenderOptionsDialog::addTexture(const QString &name)
346{
347 m_textureCombo->addItem(atext: name);
348 return m_textureCombo->count() - 1;
349}
350
351int RenderOptionsDialog::addShader(const QString &name)
352{
353 m_shaderCombo->addItem(atext: name);
354 return m_shaderCombo->count() - 1;
355}
356
357void RenderOptionsDialog::emitParameterChanged()
358{
359 for (ParameterEdit *edit : qAsConst(t&: m_parameterEdits))
360 edit->emitChange();
361}
362
363void RenderOptionsDialog::setColorParameter(QRgb color, int id)
364{
365 emit colorParameterChanged(m_parameterNames[id], color);
366}
367
368void RenderOptionsDialog::setFloatParameter(float value, int id)
369{
370 emit floatParameterChanged(m_parameterNames[id], value);
371}
372
373void RenderOptionsDialog::mouseDoubleClickEvent(QMouseEvent *event)
374{
375 if (event->button() == Qt::LeftButton)
376 emit doubleClicked();
377}
378
379//============================================================================//
380// ItemDialog //
381//============================================================================//
382
383ItemDialog::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
409void ItemDialog::triggerNewQtBox()
410{
411 emit newItemTriggered(type: QtBoxItem);
412}
413
414void ItemDialog::triggerNewCircleItem()
415{
416 emit newItemTriggered(type: CircleItem);
417}
418
419void ItemDialog::triggerNewSquareItem()
420{
421 emit newItemTriggered(type: SquareItem);
422}
423
424void ItemDialog::mouseDoubleClickEvent(QMouseEvent *event)
425{
426 if (event->button() == Qt::LeftButton)
427 emit doubleClicked();
428}
429
430//============================================================================//
431// Scene //
432//============================================================================//
433
434const static char environmentShaderText[] =
435 "uniform samplerCube env;"
436 "void main() {"
437 "gl_FragColor = textureCube(env, gl_TexCoord[1].xyz);"
438 "}";
439
440Scene::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
492Scene::~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
505void 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
603static 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.
615void 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
731void 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
757void 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
769void 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
793void 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
851void 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
880QPointF 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
886void 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
914void 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
936void 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
958void 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
971void Scene::setShader(int index)
972{
973 if (index >= 0 && index < m_fragmentShaders.size())
974 m_currentShader = index;
975}
976
977void Scene::setTexture(int index)
978{
979 if (index >= 0 && index < m_textures.size())
980 m_currentTexture = index;
981}
982
983void Scene::toggleDynamicCubemap(int state)
984{
985 if ((m_dynamicCubemap = (state == Qt::Checked)))
986 m_updateAllCubemaps = true;
987}
988
989void 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
999void 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
1009void 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

source code of qtbase/examples/widgets/graphicsview/boxes/scene.cpp