1
/****************************************************************************
2
3
This file is part of the wolfenqt project on http://qt.gitorious.org.
4
5
Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).*
6
All rights reserved.
7
8
Contact:  Nokia Corporation (qt-info@nokia.com)**
9
10
You may use this file under the terms of the BSD license as follows:
11
12
"Redistribution and use in source and binary forms, with or without
13
modification, are permitted provided that the following conditions are met:
14
* Redistributions of source code must retain the above copyright notice,
15
* this list of conditions and the following disclaimer.
16
* Redistributions in binary form must reproduce the above copyright notice,
17
* this list of conditions and the following disclaimer in the documentation
18
* and/or other materials provided with the distribution.
19
* Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the
20
* names of its contributors may be used to endorse or promote products
21
* derived from this software without specific prior written permission.
22
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
POSSIBILITY OF SUCH DAMAGE."
33
34
****************************************************************************/
35
#include "modelitem.h"
36
#include "model.h"
37
38
#include <QtGui>
39
40
#include "mazescene.h"
41
42
#ifndef QT_NO_OPENGL
43
#if !defined QT_OPENGL_ES_2 && !defined Q_WS_MAC
44
#include <GL/glew.h>
45
#endif
46
#include <QtOpenGL>
47
#ifndef GL_MULTISAMPLE
48
#define GL_MULTISAMPLE  0x809D
49
#endif
50
#endif
51
52
#include <QVector2D>
53
54
void ModelItem::updateTransform(const Camera &camera)
55
{
56
    QPointF pos(3, 7);
57
58
    QVector2D toCamera = QVector2D(camera.pos() - pos).normalized();
59
    QPointF delta(toCamera.y(), -toCamera.x());
60
61
    setPosition(pos - delta, pos + delta);
62
63
    ProjectedItem::updateTransform(camera);
64
65
    setTransform(QTransform());
66
67
    m_matrix = camera.viewMatrix();
68
    m_matrix.translate(3, 0, 7);
69
}
70
71
QRectF ModelItem::boundingRect() const
72
{
73
    if (!scene()->views().isEmpty()) {
74
        QGraphicsView *view = scene()->views().at(0);
75
        return view->mapToScene(view->rect()).boundingRect();
76
    }
77
78
    return scene()->sceneRect();
79
}
80
81
void ModelItem::updateItem()
82
{
83
    ProjectedItem::update();
84
}
85
86
const char *vertexProgram =
87
    "attribute highp    vec4    vertexCoordsArray;"
88
    "attribute highp    vec4    normalCoordsArray;"
89
    "varying   highp    vec4    normal;"
90
    "uniform   highp    mat4    pmvMatrix;"
91
    "uniform   highp    mat4    modelMatrix;"
92
    "void main(void)"
93
    "{"
94
    "        normal = modelMatrix * vec4(normalCoordsArray.xyz, 0);"
95
    "        gl_Position = pmvMatrix * vertexCoordsArray;"
96
    "}";
97
98
const char *fragmentProgram =
99
    "uniform   lowp     vec4    color;"
100
    "varying   highp    vec4    normal;"
101
    "void main() {"
102
    "   lowp vec3 toLight = normalize(vec3(-0.9, -1, 0.6));"
103
    "   gl_FragColor = color * (0.4 + 0.6 * max(dot(normalize(normal).xyz, toLight), 0.0));"
104
    "}";
105
106
QMatrix4x4 fromProjection(float fov);
107
QMatrix4x4 fromRotation(float angle, Qt::Axis axis);
108
109
void ModelItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
110
{
111
    if (!m_model || isObscured())
112
        return;
113
114
    QMatrix4x4 projectionMatrix = QMatrix4x4(painter->transform()) * fromProjection(70);
115
116
    const int delta = m_time.elapsed() - m_lastTime;
117
    m_rotation += m_angularMomentum * (delta / 1000.0);
118
    m_lastTime += delta;
119
120
    QVector3D size = m_model->size();
121
    float extent = qSqrt(2.0);
122
    float scale = 1 / qMax(size.y(), qMax(size.x() / extent, size.z() / extent));
123
    QMatrix4x4 modelMatrix;
124
    modelMatrix.scale(scale, -scale, scale);
125
126
    modelMatrix = fromRotation(m_rotation.z(), Qt::ZAxis) * modelMatrix;
127
    modelMatrix = fromRotation(m_rotation.y(), Qt::YAxis) * modelMatrix;
128
    modelMatrix = fromRotation(m_rotation.x(), Qt::XAxis) * modelMatrix;
129
130
    QTimer::singleShot(10, this, SLOT(updateItem()));
131
132
#ifndef QT_NO_OPENGL
133
    if (painter->paintEngine()->type() != QPaintEngine::OpenGL
134
        && painter->paintEngine()->type() != QPaintEngine::OpenGL2) {
135
#endif
136
        m_wireframe->setEnabled(false);
137
        m_wireframe->setChecked(false);
138
        m_wireframeEnabled = false;
139
        painter->setTransform(QTransform());
140
        painter->setPen(m_modelColor);
141
        m_model->render(painter, projectionMatrix * m_matrix * modelMatrix, m_normalsEnabled);
142
        return;
143
#ifndef QT_NO_OPENGL
144
    }
145
146
    m_wireframe->setEnabled(true);
147
148
    painter->beginNativePainting();
149
150
#ifdef QT_OPENGL_ES_2
151
    glClearDepthf(0);
152
#else
153
    glClearDepth(0);
154
#endif
155
    glClear(GL_DEPTH_BUFFER_BIT);
156
157
    if (!m_program) {
158
#if !defined QT_OPENGL_ES_2 && !defined Q_WS_MAC
159
        glewInit();
160
#endif
161
        m_program = new QGLShaderProgram;
162
        m_program->addShaderFromSourceCode(QGLShader::Fragment, fragmentProgram);
163
        m_program->addShaderFromSourceCode(QGLShader::Vertex, vertexProgram);
164
        m_program->bindAttributeLocation("vertexCoordsArray", 0);
165
        m_program->bindAttributeLocation("normalCoordsArray", 1);
166
        m_program->link();
167
168
        if (!m_program->isLinked())
169
            qDebug() << m_program->log();
170
    }
171
172
    qreal ortho[] = {
173
        2.0f / painter->device()->width(), 0, 0, -1,
174
        0, -2.0f / painter->device()->height(), 0, 1,
175
        0, 0, -1, 0,
176
        0, 0, 0, 1
177
    };
178
179
    m_program->bind();
180
    m_program->setUniformValue("color", m_modelColor);
181
    m_program->setUniformValue("pmvMatrix", QMatrix4x4(ortho) * projectionMatrix * m_matrix * modelMatrix);
182
    m_program->setUniformValue("modelMatrix", modelMatrix);
183
    m_model->render(m_wireframeEnabled, m_normalsEnabled);
184
    m_program->release();
185
186
    painter->endNativePainting();
187
#endif
188
}
189
190
191
ModelItem::ModelItem()
192
    : ProjectedItem(QRectF(), false, false)
193
    , m_wireframeEnabled(false)
194
    , m_normalsEnabled(false)
195
    , m_modelColor(153, 255, 0)
196
    , m_model(0)
197
    , m_lastTime(0)
198
    , m_distance(1.4f)
199
    , m_angularMomentum(0, 40, 0)
200
#ifndef QT_NO_OPENGL
201
    , m_program(0)
202
#endif
203
{
204
    setLayout(new QVBoxLayout);
205
206
    m_modelButton = new QPushButton(tr("Load model"));
207
    connect(m_modelButton, SIGNAL(clicked()), this, SLOT(loadModel()));
208
#ifndef QT_NO_CONCURRENT
209
    connect(&m_modelLoader, SIGNAL(finished()), this, SLOT(modelLoaded()));
210
#endif
211
    layout()->addWidget(m_modelButton);
212
213
    m_wireframe = new QCheckBox(tr("Render as wireframe"));
214
    connect(m_wireframe, SIGNAL(toggled(bool)), this, SLOT(enableWireframe(bool)));
215
    layout()->addWidget(m_wireframe);
216
217
    QCheckBox *normals = new QCheckBox(tr("Display normals vectors"));
218
    connect(normals, SIGNAL(toggled(bool)), this, SLOT(enableNormals(bool)));
219
    layout()->addWidget(normals);
220
221
    QPushButton *colorButton = new QPushButton(tr("Choose model color"));
222
    connect(colorButton, SIGNAL(clicked()), this, SLOT(setModelColor()));
223
    layout()->addWidget(colorButton);
224
225
    loadModel(QLatin1String("qt.obj"));
226
    m_time.start();
227
}
228
229
static Model *loadModel(const QString &filePath)
230
{
231
    return new Model(filePath);
232
}
233
234
void ModelItem::loadModel()
235
{
236
    loadModel(QFileDialog::getOpenFileName(0, tr("Choose model"), QString(), QLatin1String("*.obj")));
237
}
238
239
void ModelItem::loadModel(const QString &filePath)
240
{
241
    if (filePath.isEmpty())
242
        return;
243
244
    m_modelButton->setEnabled(false);
245
    QApplication::setOverrideCursor(Qt::BusyCursor);
246
#ifndef QT_NO_CONCURRENT
247
    m_modelLoader.setFuture(QtConcurrent::run(::loadModel, filePath));
248
#else
249
    setModel(::loadModel(filePath));
250
    modelLoaded();
251
#endif
252
}
253
254
void ModelItem::modelLoaded()
255
{
256
#ifndef QT_NO_CONCURRENT
257
    setModel(m_modelLoader.result());
258
#endif
259
    m_modelButton->setEnabled(true);
260
    QApplication::restoreOverrideCursor();
261
}
262
263
void ModelItem::setModel(Model *model)
264
{
265
    delete m_model;
266
    m_model = model;
267
268
    ProjectedItem::update();
269
}
270
271
void ModelItem::enableWireframe(bool enabled)
272
{
273
    m_wireframeEnabled = enabled;
274
    ProjectedItem::update();
275
}
276
277
void ModelItem::enableNormals(bool enabled)
278
{
279
    m_normalsEnabled = enabled;
280
    ProjectedItem::update();
281
}
282
283
void ModelItem::setModelColor()
284
{
285
    const QColor color = QColorDialog::getColor(m_modelColor);
286
    if (color.isValid()) {
287
        m_modelColor = color;
288
        ProjectedItem::update();
289
    }
290
}