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 "mazescene.h"
36
37
#include <QCheckBox>
38
#include <QComboBox>
39
#include <QGraphicsProxyWidget>
40
#include <QPainter>
41
#include <QPushButton>
42
#include <QKeyEvent>
43
#include <QTimer>
44
#include <QVBoxLayout>
45
#if 0
46
#include <QWebView>
47
#endif
48
#include <QGraphicsWebView>
49
50
#include <qmath.h>
51
#include <qdebug.h>
52
53
#include <limits>
54
55
#include "scriptwidget.h"
56
#include "entity.h"
57
#include "modelitem.h"
58
59
#include <QVector3D>
60
61
#ifdef USE_PHONON
62
#include "mediaplayer/mediaplayer.h"
63
#endif
64
65
#ifndef QT_NO_OPENGL
66
#include <QGLWidget>
67
#endif
68
69
View::View()
70
    : m_scene(0)
71
    , m_firstPaint(true)
72
{
73
}
74
75
void View::setScene(MazeScene *scene)
76
{
77
    QGraphicsView::setScene(scene);
78
79
    m_scene = scene;
80
    m_scene->viewResized(this);
81
}
82
83
void View::resizeEvent(QResizeEvent *)
84
{
85
    resetMatrix();
86
    qreal factor = width() / 4.0;
87
    scale(factor, factor);
88
89
    if (m_scene)
90
        m_scene->viewResized(this);
91
}
92
93
void View::paintEvent(QPaintEvent *event)
94
{
95
    if (m_firstPaint) {
96
        QPainter p(viewport());
97
	static_cast<MazeScene *>(scene())->setAcceleratedViewport(
98
            p.paintEngine()->type() == QPaintEngine::OpenGL
99
            || p.paintEngine()->type() == QPaintEngine::OpenGL2);
100
        m_firstPaint = false;
101
    }
102
103
    QGraphicsView::paintEvent(event);
104
}
105
106
Light::Light(const QPointF &pos, qreal intensity)
107
    : m_pos(pos)
108
    , m_intensity(intensity)
109
{
110
}
111
112
qreal Light::intensityAt(const QPointF &pos) const
113
{
114
    const qreal quadraticIntensity = 150 * m_intensity;
115
    const qreal linearIntensity = 30 * m_intensity;
116
117
    const qreal d = QLineF(m_pos, pos).length();
118
    return quadraticIntensity / (d * d)
119
        + linearIntensity / d;
120
}
121
122
QMatrix4x4 fromRotation(float angle, Qt::Axis axis)
123
{
124
    QMatrix4x4 m;
125
    if (axis == Qt::XAxis)
126
        m.rotate(angle, QVector3D(1, 0, 0));
127
    else if (axis == Qt::YAxis)
128
        m.rotate(-angle, QVector3D(0, 1, 0));
129
    else if (axis == Qt::ZAxis)
130
        m.rotate(angle, QVector3D(0, 0, 1));
131
    return m;
132
}
133
134
QMatrix4x4 fromProjection(float fovAngle)
135
{
136
    float fov = qCos(fovAngle / 2) / qSin(fovAngle / 2);
137
138
    float zNear = 0.01;
139
    float zFar = 1000;
140
141
    float m33 = (zNear + zFar) / (zNear - zFar);
142
    float m34 = (2 * zNear * zFar) / (zNear - zFar);
143
144
    qreal data[] =
145
    {
146
        fov, 0, 0, 0,
147
        0, fov, 0, 0,
148
        0, 0, m33, m34,
149
        0, 0, -1, 0
150
    };
151
    return QMatrix4x4(data);
152
}
153
154
class WalkingItem : public QGraphicsPixmapItem
155
{
156
public:
157
    WalkingItem(MazeScene *scene);
158
159
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
160
161
    bool walking() const { return m_walking; }
162
163
private:
164
    void updatePixmap();
165
166
    MazeScene *m_scene;
167
    bool m_walking;
168
169
    QPixmap m_walkingPixmap;
170
    QPixmap m_standingPixmap;
171
};
172
173
QPixmap colorize(const QPixmap &source, const QColor &color)
174
{
175
    QImage temp(source.size(), QImage::Format_ARGB32_Premultiplied);
176
177
    temp.fill(0x0);
178
    QPainter p(&temp);
179
    p.drawPixmap(0, 0, source);
180
    p.setCompositionMode(QPainter::CompositionMode_SourceIn);
181
    p.fillRect(temp.rect(), color);
182
    p.end();
183
184
    return QPixmap::fromImage(temp);
185
}
186
187
WalkingItem::WalkingItem(MazeScene *scene)
188
    : m_scene(scene)
189
    , m_walking(false)
190
    , m_walkingPixmap(colorize(QPixmap("walking.png"), QColor(Qt::green).darker()))
191
    , m_standingPixmap(colorize(QPixmap("standing.png"), QColor(Qt::green).darker()))
192
{
193
    setShapeMode(BoundingRectShape);
194
    updatePixmap();
195
}
196
197
void WalkingItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
198
{
199
    event->accept();
200
    m_walking = !m_walking;
201
    updatePixmap();
202
}
203
204
void WalkingItem::updatePixmap()
205
{
206
    if (m_walking)
207
        setPixmap(m_walkingPixmap);
208
    else
209
        setPixmap(m_standingPixmap);
210
}
211
212
MazeScene::MazeScene(const QVector<Light> &lights, const char *map, int width, int height)
213
    : m_lights(lights)
214
    , m_walkingVelocity(0)
215
    , m_strafingVelocity(0)
216
    , m_turningSpeed(0)
217
    , m_pitchSpeed(0)
218
    , m_deltaYaw(0)
219
    , m_deltaPitch(0)
220
    , m_simulationTime(0)
221
    , m_walkTime(0)
222
    , m_width(width)
223
    , m_height(height)
224
    , m_player(0)
225
    , m_accelerated(false)
226
{
227
    m_camera.setPos(QPointF(1.5, 1.5));
228
    m_camera.setYaw(0.1);
229
230
    m_doorAnimation = new QTimeLine(1000, this);
231
    m_doorAnimation->setUpdateInterval(20);
232
    connect(m_doorAnimation, SIGNAL(valueChanged(qreal)), this, SLOT(moveDoors(qreal)));
233
234
    QMap<char, int> types;
235
    types[' '] = -2;
236
    types['-'] = -1;
237
    types['#'] = 0;
238
    types['&'] = 1;
239
    types['@'] = 2;
240
    types['%'] = 3;
241
    types['$'] = 4;
242
    types['?'] = 5;
243
    types['!'] = 6;
244
    types['='] = 7;
245
    types['*'] = 8;
246
    types['/'] = 9;
247
    types['.'] = 10;
248
249
    int type;
250
    for (int y = 0; y < height; ++y) {
251
        for (int x = 0; x < width; ++x) {
252
            type = types[map[y*width+x]];
253
            if (type >= 0)
254
                continue;
255
256
            type = types[map[(y-1)*width+x]];
257
            if (type >= -1)
258
                addWall(QPointF(x, y), QPointF(x+1, y), type);
259
260
            type = types[map[(y+1)*width+x]];
261
            if (type >= -1)
262
                addWall(QPointF(x+1, y+1), QPointF(x, y+1), type);
263
264
            type = types[map[y*width+x-1]];
265
            if (type >= -1)
266
                addWall(QPointF(x, y+1), QPointF(x, y), type);
267
268
            type = types[map[y*width+x+1]];
269
            if (type >= -1)
270
                addWall(QPointF(x+1, y), QPointF(x+1, y+1), type);
271
        }
272
    }
273
274
    QTimer *timer = new QTimer(this);
275
    timer->setInterval(20);
276
    timer->start();
277
    connect(timer, SIGNAL(timeout()), this, SLOT(move()));
278
279
    m_time.start();
280
    updateTransforms();
281
    updateRenderer();
282
283
    m_walkingItem = new WalkingItem(this);
284
    m_walkingItem->scale(0.008, 0.008);
285
    m_walkingItem->setZValue(100000);
286
287
    addItem(m_walkingItem);
288
}
289
290
void MazeScene::setAcceleratedViewport(bool accelerated)
291
{
292
    m_accelerated = accelerated;
293
294
    if (!accelerated)
295
        QTimer::singleShot(0, this, SLOT(toggleRenderer()));
296
297
    updateRenderer();
298
}
299
300
void MazeScene::viewResized(QGraphicsView *view)
301
{
302
    QRectF bounds = m_walkingItem->sceneBoundingRect();
303
    QPointF bottomLeft = view->mapToScene(QPoint(5, view->height() - 5));
304
305
    m_walkingItem->setPos(bottomLeft.x(), bottomLeft.y() - bounds.height());
306
}
307
308
void MazeScene::addProjectedItem(ProjectedItem *item)
309
{
310
    addItem(item);
311
    m_projectedItems << item;
312
}
313
314
void MazeScene::addWall(const QPointF &a, const QPointF &b, int type)
315
{
316
    WallItem *item = new WallItem(this, a, b, type);
317
#ifdef USE_PHONON
318
    if (item->childItem() && item->type() == 7) {
319
        m_playerPos = (a + b ) / 2;
320
        m_player = static_cast<MediaPlayer *>(item->childItem()->widget());
321
    }
322
#endif
323
324
#if 0
325
    QGraphicsProxyWidget *proxy = item->childItem();
326
    QWebView *view = proxy ? qobject_cast<QWebView *>(proxy->widget()) : 0;
327
    if (view) {
328
        connect(view, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished()));
329
        proxy->setVisible(false);
330
    }
331
#endif
332
    item->setVisible(false);
333
    addProjectedItem(item);
334
    m_walls << item;
335
336
    if (type == -1)
337
        m_doors << item;
338
339
    setSceneRect(-1, -1, 2, 2);
340
    if (item->childItem()) {
341
        QObject *widget = item->childItem()->widget()->children().value(0);
342
        QPushButton *button = qobject_cast<QPushButton *>(widget);
343
        if (button)
344
            m_buttons << button;
345
    }
346
}
347
348
void MazeScene::loadFinished()
349
{
350
    QWidget *widget = qobject_cast<QWidget *>(sender());
351
352
    if (widget) {
353
        foreach (WallItem *item, m_walls) {
354
            QGraphicsProxyWidget *proxy = item->childItem();
355
            if (proxy && proxy->widget() == widget)
356
                proxy->setVisible(true);
357
        }
358
    }
359
}
360
361
static inline QTransform rotatingTransform(qreal angle)
362
{
363
    QTransform transform;
364
    transform.rotate(angle);
365
    return transform;
366
}
367
368
void Camera::setPitch(qreal pitch)
369
{
370
    m_pitch = qBound(qreal(-30), pitch, qreal(30));
371
    m_matrixDirty = true;
372
}
373
374
void Camera::setYaw(qreal yaw)
375
{
376
    m_yaw = yaw;
377
    m_matrixDirty = true;
378
}
379
380
void Camera::setPos(const QPointF &pos)
381
{
382
    m_pos = pos;
383
    m_matrixDirty = true;
384
}
385
386
void Camera::setFov(qreal fov)
387
{
388
    m_fov = fov;
389
    m_matrixDirty = true;
390
}
391
392
void Camera::setTime(qreal time)
393
{
394
    m_time = time;
395
    m_matrixDirty = true;
396
}
397
398
399
const QMatrix4x4 &Camera::viewMatrix() const
400
{
401
    updateMatrix();
402
    return m_viewMatrix;
403
}
404
405
const QMatrix4x4 &Camera::viewProjectionMatrix() const
406
{
407
    updateMatrix();
408
    return m_viewProjectionMatrix;
409
}
410
411
void Camera::updateMatrix() const
412
{
413
    if (!m_matrixDirty)
414
        return;
415
416
    m_matrixDirty = false;
417
418
    QMatrix4x4 m;
419
    m.scale(-1, 1, 1);
420
    m *= fromRotation(m_yaw + 180, Qt::YAxis);
421
    m.translate(-m_pos.x(), 0.04 * qSin(10 * m_time) + 0.1, -m_pos.y());
422
    m = fromRotation(m_pitch, Qt::XAxis) * m;
423
    m_viewMatrix = m;
424
    m_viewProjectionMatrix = fromProjection(m_fov) * m_viewMatrix;
425
}
426
427
void MazeScene::drawBackground(QPainter *painter, const QRectF &)
428
{
429
    static QImage floor = QImage("floor.png").convertToFormat(QImage::Format_RGB32);
430
    QBrush floorBrush(floor);
431
432
    static QImage ceiling = QImage("ceiling.png").convertToFormat(QImage::Format_RGB32);
433
    QBrush ceilingBrush(ceiling);
434
435
    QTransform brushScale;
436
    brushScale.scale(0.5 / floor.width(), 0.5 / floor.height());
437
    floorBrush.setTransform(brushScale);
438
    ceilingBrush.setTransform(brushScale);
439
440
    const QRectF r(1, 1, m_width-2, m_height-2);
441
442
    QMatrix4x4 m = m_camera.viewProjectionMatrix();
443
444
    QMatrix4x4 floorMatrix = m;
445
    floorMatrix.translate(0, 0.5, 0);
446
    floorMatrix *= fromRotation(90, Qt::XAxis);
447
448
    painter->save();
449
    painter->setTransform(floorMatrix.toTransform(0), true);
450
    painter->fillRect(r, floorBrush);
451
    painter->restore();
452
453
    QMatrix4x4 ceilingMatrix = m;
454
    ceilingMatrix.translate(0, -0.5, 0);
455
    ceilingMatrix *= fromRotation(90, Qt::XAxis);
456
457
    painter->save();
458
    painter->setTransform(ceilingMatrix.toTransform(0), true);
459
    painter->fillRect(r, ceilingBrush);
460
    painter->restore();
461
}
462
463
void MazeScene::addEntity(Entity *entity)
464
{
465
    addProjectedItem(entity);
466
    m_entities << entity;
467
}
468
469
ProjectedItem::ProjectedItem(const QRectF &bounds, bool shadow, bool opaque)
470
    : m_bounds(bounds)
471
    , m_shadowItem(0)
472
    , m_opaque(opaque)
473
    , m_obscured(false)
474
{
475
    if (shadow) {
476
        m_shadowItem = new QGraphicsRectItem(bounds, this);
477
        m_shadowItem->setPen(Qt::NoPen);
478
        m_shadowItem->setZValue(10);
479
    }
480
481
    m_targetRect = m_bounds;
482
}
483
484
void ProjectedItem::setOpaque(bool opaque)
485
{
486
    m_opaque = opaque;
487
}
488
489
bool ProjectedItem::isOpaque() const
490
{
491
    return m_opaque;
492
}
493
494
void ProjectedItem::setPosition(const QPointF &a, const QPointF &b)
495
{
496
    m_a = a;
497
    m_b = b;
498
}
499
500
void ProjectedItem::setLightingEnabled(bool enable)
501
{
502
    if (m_shadowItem)
503
        m_shadowItem->setVisible(enable);
504
}
505
506
class ProxyWidget : public QGraphicsProxyWidget
507
{
508
public:
509
    ProxyWidget(QGraphicsItem *parent = 0)
510
        : QGraphicsProxyWidget(parent)
511
    {
512
    }
513
514
protected:
515
    QVariant itemChange(GraphicsItemChange change, const QVariant & value)
516
    {
517
        // we want the position of proxy widgets to stay at (0, 0)
518
        // so ignore the position changes from the native QWidget
519
        if (change == ItemPositionChange)
520
            return QPointF();
521
        else
522
            return QGraphicsProxyWidget::itemChange(change, value);
523
    }
524
};
525
526
527
WallItem::WallItem(MazeScene *scene, const QPointF &a, const QPointF &b, int type)
528
    : ProjectedItem(QRectF(-0.5, -0.5, 1.0, 1.0))
529
    , m_type(type)
530
{
531
    setPosition(a, b);
532
533
    static QImage brown = QImage("brown.png").convertToFormat(QImage::Format_RGB32);
534
    static QImage book = QImage("book.png").convertToFormat(QImage::Format_RGB32);
535
    static QImage door = QImage("door.png").convertToFormat(QImage::Format_RGB32);
536
537
    switch (type) {
538
    case -1:
539
        setImage(door);
540
        break;
541
    case 1:
542
        setImage(book);
543
        break;
544
    case 2:
545
        setOpaque(false);
546
        break;
547
    default:
548
        setImage(brown);
549
        break;
550
    }
551
552
    m_scale = 0.8;
553
554
    m_childItem = 0;
555
    QWidget *childWidget = 0;
556
    if (type == 3 && a.y() == b.y()) {
557
        QWidget *widget = new QWidget;
558
        QPushButton *button = new QPushButton("Open Sesame", widget);
559
        QObject::connect(button, SIGNAL(clicked()), scene, SLOT(toggleDoors()));
560
        widget->setLayout(new QVBoxLayout);
561
        widget->layout()->addWidget(button);
562
        childWidget = widget;
563
        m_scale = 0.3;
564
    } else if (type == 4) {
565
        View *view = new View;
566
        view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
567
        view->resize(480, 320); // not soo big
568
        view->setViewport(new QWidget); // no OpenGL here
569
        childWidget = view;
570
    } else if (type == 5) {
571
        Entity *entity = new Entity(QPointF(6.5, 2.5));
572
        scene->addEntity(entity);
573
        childWidget = new ScriptWidget(scene, entity);
574
    } else if (type == 7) {
575
#ifdef USE_PHONON
576
        Q_INIT_RESOURCE(mediaplayer);
577
        childWidget = new MediaPlayer(QString());
578
        m_scale = 0.6;
579
#endif
580
    } else if (type == 8) {
581
        ModelItem *dialog = new ModelItem;
582
        childWidget = dialog;
583
        scene->addProjectedItem(dialog);
584
        m_scale = 0.5;
585
    } else if (type == 9) {
586
        if (a.x() > b.x()) {
587
#if 0
588
            QWebView *view = new QWebView;
589
            view->setUrl(QUrl(QLatin1String("http://www.google.com")));
590
            childWidget = view;
591
#endif
592
            QGraphicsWebView *view = new QGraphicsWebView(this);
593
            view->setCacheMode(QGraphicsItem::ItemCoordinateCache);
594
            view->setResizesToContents(false);
595
            view->setGeometry(QRectF(0, 0, 800, 600));
596
            view->setUrl(QUrl(QLatin1String("http://www.google.com")));
597
598
            QRectF rect = view->boundingRect();
599
            QPointF center = rect.center();
600
            qreal scale = qMin(m_scale / rect.width(), m_scale / rect.height());
601
            view->translate(0, -0.05);
602
            view->scale(scale, scale);
603
            view->translate(-center.x(), -center.y());
604
        }
605
    } else if (type == 10) {
606
#ifndef QT_NO_OPENGL
607
        QWidget *widget = new QWidget;
608
        QCheckBox *checkBox = new QCheckBox("Use OpenGL", widget);
609
        checkBox->setChecked(true);
610
        QObject::connect(checkBox, SIGNAL(toggled(bool)), scene, SLOT(toggleRenderer()), Qt::QueuedConnection);
611
        widget->setLayout(new QVBoxLayout);
612
        widget->layout()->addWidget(checkBox);
613
        childWidget = widget;
614
        m_scale = 0.2;
615
#endif
616
    }
617
618
    if (!childWidget)
619
        return;
620
621
    childWidget->installEventFilter(scene);
622
623
    m_childItem = new ProxyWidget(this);
624
    m_childItem->setWidget(childWidget);
625
    m_childItem->setCacheMode(QGraphicsItem::ItemCoordinateCache);
626
627
    QRectF rect = m_childItem->boundingRect();
628
    QPointF center = rect.center();
629
630
    qreal scale = qMin(m_scale / rect.width(), m_scale / rect.height());
631
    m_childItem->translate(0, -0.05);
632
    m_childItem->scale(scale, scale);
633
    m_childItem->translate(-center.x(), -center.y());
634
}
635
636
bool MazeScene::eventFilter(QObject *target, QEvent *event)
637
{
638
    QWidget *widget = qobject_cast<QWidget *>(target);
639
    if (!widget || event->type() != QEvent::Resize)
640
        return false;
641
642
    foreach (WallItem *item, m_walls) {
643
        QGraphicsProxyWidget *proxy = item->childItem();
644
        if (proxy && proxy->widget() == widget)
645
            item->childResized();
646
    }
647
648
    return false;
649
}
650
651
void WallItem::childResized()
652
{
653
    QRectF rect = m_childItem->boundingRect();
654
655
    QPointF center = rect.center();
656
657
    qreal scale = qMin(m_scale / rect.width(), m_scale / rect.height());
658
    m_childItem->resetMatrix();
659
    m_childItem->translate(0, -0.05);
660
    m_childItem->scale(scale, scale);
661
    m_childItem->translate(-center.x(), -center.y());
662
663
    // refresh cache size
664
    m_childItem->setCacheMode(QGraphicsItem::NoCache);
665
    m_childItem->setCacheMode(QGraphicsItem::ItemCoordinateCache);
666
}
667
668
void ProjectedItem::updateLighting(const QVector<Light> &lights, bool useConstantLight)
669
{
670
    if (!m_shadowItem)
671
        return;
672
673
    if (useConstantLight) {
674
        m_shadowItem->setBrush(QColor(0, 0, 0, 100));
675
        return;
676
    }
677
678
    const qreal constantIntensity = 80;
679
    qreal la = constantIntensity;
680
    qreal lb = constantIntensity;
681
    foreach (const Light &light, lights) {
682
        la += light.intensityAt(m_b);
683
        lb += light.intensityAt(m_a);
684
    }
685
686
    int va = qMax(0, 255 - int(la));
687
    int vb = qMax(0, 255 - int(lb));
688
689
    if (va == vb) {
690
        m_shadowItem->setBrush(QColor(0, 0, 0, va));
691
    } else {
692
        const QRectF rect = boundingRect();
693
        QLinearGradient g(rect.topLeft(), rect.topRight());
694
695
        g.setColorAt(0, QColor(0, 0, 0, va));
696
        g.setColorAt(1, QColor(0, 0, 0, vb));
697
698
        m_shadowItem->setBrush(g);
699
    }
700
}
701
702
QRectF ProjectedItem::boundingRect() const
703
{
704
    return m_bounds;
705
}
706
707
void ProjectedItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
708
{
709
    if (!m_image.isNull()) {
710
        QRectF target = m_targetRect.translated(0.5, 0.5);
711
        QRectF source = QRectF(0, 0, m_image.width() * (1 - target.x()), m_image.height());
712
        painter->drawImage(m_targetRect, m_image, source);
713
    }
714
}
715
716
void ProjectedItem::setAnimationTime(qreal time)
717
{
718
    // hacky way of handling door animation
719
    QRectF rect = boundingRect();
720
    m_targetRect = QRectF(QPointF(rect.left() + rect.width() * time, rect.top()),
721
                          rect.bottomRight());
722
    if (m_shadowItem)
723
        m_shadowItem->setRect(m_targetRect);
724
    update();
725
}
726
727
void ProjectedItem::setImage(const QImage &image)
728
{
729
    m_image = image;
730
    update();
731
}
732
733
void ProjectedItem::setObscured(bool obscured)
734
{
735
    m_obscured = obscured;
736
}
737
738
bool ProjectedItem::isObscured() const
739
{
740
    return m_obscured;
741
}
742
743
void ProjectedItem::updateTransform(const Camera &camera)
744
{
745
    if (!m_obscured) {
746
        QTransform rotation;
747
        rotation *= QTransform().translate(-camera.pos().x(), -camera.pos().y());
748
        rotation *= rotatingTransform(camera.yaw());
749
        QPointF center = (m_a + m_b) / 2;
750
751
        QPointF ca = rotation.map(m_a);
752
        QPointF cb = rotation.map(m_b);
753
754
        if (ca.y() > 0 || cb.y() > 0) {
755
            QMatrix4x4 m = camera.viewProjectionMatrix();
756
            m.translate(center.x(), 0, center.y());
757
            m *= fromRotation(-QLineF(m_b, m_a).angle(), Qt::YAxis);
758
759
            qreal zm = QLineF(camera.pos(), center).length();
760
761
            setVisible(true);
762
            setZValue(-zm);
763
            setTransform(m.toTransform(0));
764
            return;
765
        }
766
    }
767
768
    // hide the item by placing it far outside the scene
769
    // we could use setVisible() but that causes unnecessary
770
    // update to cahced items
771
    QTransform transform;
772
    transform.translate(-1000, -1000);
773
    setTransform(transform);
774
}
775
776
777
void MazeScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
778
{
779
    if (focusItem()) {
780
        QGraphicsScene::mouseMoveEvent(event);
781
        return;
782
    }
783
784
    if (event->buttons() & Qt::LeftButton) {
785
        QPointF delta(event->scenePos() - event->lastScenePos());
786
        m_deltaYaw += delta.x() * 80;
787
        m_deltaPitch -= delta.y() * 80;
788
    }
789
}
790
791
void MazeScene::keyPressEvent(QKeyEvent *event)
792
{
793
    if (handleKey(event->key(), true)) {
794
        event->accept();
795
        return;
796
    }
797
798
    QGraphicsScene::keyPressEvent(event);
799
}
800
801
void MazeScene::keyReleaseEvent(QKeyEvent *event)
802
{
803
    if (handleKey(event->key(), false)) {
804
        event->accept();
805
        return;
806
    }
807
808
    QGraphicsScene::keyReleaseEvent(event);
809
}
810
811
bool MazeScene::handleKey(int key, bool pressed)
812
{
813
    if (focusItem())
814
        return false;
815
816
    switch (key) {
817
    case Qt::Key_Left:
818
    case Qt::Key_Q:
819
        m_turningSpeed = (pressed ? -0.5 : 0.0);
820
        return true;
821
    case Qt::Key_Right:
822
    case Qt::Key_E:
823
        m_turningSpeed = (pressed ? 0.5 : 0.0);
824
        return true;
825
    case Qt::Key_Down:
826
        m_pitchSpeed = (pressed ? 0.5 : 0.0);
827
        return true;
828
    case Qt::Key_Up:
829
        m_pitchSpeed = (pressed ? -0.5 : 0.0);
830
        return true;
831
    case Qt::Key_S:
832
        m_walkingVelocity = (pressed ? -0.01 : 0.0);
833
        return true;
834
    case Qt::Key_W:
835
        m_walkingVelocity = (pressed ? 0.01 : 0.0);
836
        return true;
837
    case Qt::Key_A:
838
        m_strafingVelocity = (pressed ? -0.01 : 0.0);
839
        return true;
840
    case Qt::Key_D:
841
        m_strafingVelocity = (pressed ? 0.01 : 0.0);
842
        return true;
843
    }
844
845
    return false;
846
}
847
848
static inline QRectF rectFromPoint(const QPointF &point, qreal size)
849
{
850
    return QRectF(point, point).adjusted(-size/2, -size/2, size/2, size/2);
851
}
852
853
bool MazeScene::blocked(const QPointF &pos, Entity *me) const
854
{
855
    const QRectF rect = rectFromPoint(pos, me ? 0.7 : 0.25);
856
857
    foreach (WallItem *item, m_walls) {
858
        if (item->type() == 6
859
            || (item->type() == -1 && m_doorAnimation->state() != QTimeLine::Running
860
               && m_doorAnimation->direction() == QTimeLine::Backward))
861
            continue;
862
863
        const QPointF a = item->a();
864
        const QPointF b = item->b();
865
866
        QRectF wallRect = QRectF(a, b).adjusted(-0.01, -0.01, 0.01, 0.01);
867
868
        if (wallRect.intersects(rect))
869
            return true;
870
    }
871
872
    foreach (Entity *entity, m_entities) {
873
        if (entity == me)
874
            continue;
875
        QRectF entityRect = rectFromPoint(entity->pos(), 0.8);
876
877
        if (entityRect.intersects(rect))
878
            return true;
879
    }
880
881
    if (me) {
882
        QRectF cameraRect = rectFromPoint(m_camera.pos(), 0.4);
883
884
        if (cameraRect.intersects(rect))
885
            return true;
886
    }
887
888
    return false;
889
}
890
891
bool MazeScene::tryMove(QPointF &pos, const QPointF &delta, Entity *entity) const
892
{
893
    const QPointF old = pos;
894
895
    if (delta.x() != 0 && !blocked(pos + QPointF(delta.x(), 0), entity))
896
        pos.setX(pos.x() + delta.x());
897
898
    if (delta.y() != 0 && !blocked(pos + QPointF(0, delta.y()), entity))
899
        pos.setY(pos.y() + delta.y());
900
901
    return pos != old;
902
}
903
904
struct Span
905
{
906
    ProjectedItem *item;
907
908
    // screen coordinates
909
    float sx1;
910
    float sx2;
911
912
    float cy;
913
};
914
915
int split(QList<Span> &list, float x)
916
{
917
    for (int i = 0; i < list.size(); ++i) {
918
        Span &span = list[i];
919
        if (span.sx2 == x)
920
            return i+1;
921
922
        if (span.sx1 <= x && span.sx2 > x) {
923
            Span split = span;
924
            span.sx2 = x;
925
            split.sx1 = x;
926
            list.insert(i+1, split);
927
            return i+1;
928
        }
929
    }
930
    return -1;
931
}
932
933
bool insertSpan(QList<Span> &list, const Span &span, bool checkOnly)
934
{
935
    int left = split(list, span.sx1);
936
    int right = split(list, span.sx2);
937
938
    bool visible = false;
939
    for (int i = left; i < right; ++i) {
940
        Span &s = list[i];
941
        if (s.cy > span.cy) {
942
            visible = true;
943
            if (!checkOnly) {
944
                s.item = span.item;
945
                s.cy = span.cy;
946
            }
947
        }
948
    }
949
    return visible;
950
}
951
952
bool insertProjectedItem(QList<Span> &list, ProjectedItem *item, const QTransform &cameraTransform, bool checkOnly)
953
{
954
    QPointF ca = cameraTransform.map(item->a());
955
    QPointF cb = cameraTransform.map(item->b());
956
957
    if (ca.y() <= 0 && cb.y() <= 0)
958
        return false;
959
960
    const float clip = 0.0001;
961
    if (ca.y() <= 0) {
962
        float t = (clip - ca.y()) / (cb.y() - ca.y());
963
        ca.setX(ca.x() + t * (cb.x() - ca.x()));
964
        ca.setY(clip);
965
    } else if(cb.y() <= 0) {
966
        float t = (clip - ca.y()) / (cb.y() - ca.y());
967
        cb.setX(ca.x() + t * (cb.x() - ca.x()));
968
        cb.setY(clip);
969
    }
970
971
    Span span;
972
    span.item = item;
973
    span.sx1 = ca.x() / ca.y();
974
    span.sx2 = cb.x() / cb.y();
975
    span.cy = (ca.y() + cb.y()) * 0.5f;
976
977
    if (span.sx1 >= span.sx2)
978
        qSwap(span.sx1, span.sx2);
979
980
    return insertSpan(list, span, checkOnly);
981
}
982
983
void MazeScene::updateTransforms()
984
{
985
    Span span;
986
    span.item = 0;
987
    span.sx1 = -std::numeric_limits<float>::infinity();
988
    span.sx2 =  std::numeric_limits<float>::infinity();
989
    span.cy = std::numeric_limits<float>::infinity();
990
991
    QList<Span> visibleList;
992
    visibleList << span;
993
994
    QTransform rotation;
995
    rotation *= QTransform().translate(-m_camera.pos().x(), -m_camera.pos().y());
996
    rotation *= rotatingTransform(m_camera.yaw());
997
998
    // first add all opaque items
999
    foreach (ProjectedItem *item, m_projectedItems) {
1000
        if (item->isOpaque()) {
1001
            item->setObscured(true);
1002
            insertProjectedItem(visibleList, item, rotation, false);
1003
        }
1004
    }
1005
1006
    // mark visible opaque items
1007
    for (int i = 0; i < visibleList.size(); ++i)
1008
        if (visibleList.at(i).item)
1009
            visibleList.at(i).item->setObscured(false);
1010
1011
    // now add all non-opaque items
1012
    foreach (ProjectedItem *item, m_projectedItems) {
1013
        if (!item->isOpaque())
1014
            item->setObscured(!insertProjectedItem(visibleList, item, rotation, true));
1015
    }
1016
1017
    foreach (ProjectedItem *item, m_projectedItems)
1018
        item->updateTransform(m_camera);
1019
1020
    foreach (WallItem *item, m_walls) {
1021
        if (item->isVisible() && !item->isObscured()) {
1022
            // embed recursive scene
1023
            if (QGraphicsProxyWidget *child = item->childItem()) {
1024
                View *view = qobject_cast<View *>(child->widget());
1025
                if (view && !view->scene()) {
1026
                    const char *map =
1027
                        "#$###"
1028
                        "#   #"
1029
                        "# @ #"
1030
                        "#   #"
1031
                        "#####";
1032
                    QVector<Light> lights;
1033
                    lights << Light(QPointF(2.5, 2.5), 1)
1034
                           << Light(QPointF(1.5, 1.5), 0.4);
1035
                    MazeScene *embeddedScene = new MazeScene(lights, map, 5, 5);
1036
                    view->setScene(embeddedScene);
1037
                    view->setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing);
1038
                }
1039
            }
1040
        }
1041
    }
1042
1043
#ifdef USE_PHONON
1044
    if (m_player) {
1045
        qreal distance = QLineF(m_camera.pos(), m_playerPos).length();
1046
        m_player->setVolume(qPow(2, -0.3 * distance));
1047
    }
1048
#endif
1049
    setFocusItem(0); // setVisible(true) might give focus to one of the items
1050
    update();
1051
}
1052
1053
void MazeScene::move()
1054
{
1055
    QSet<Entity *> movedEntities;
1056
    long elapsed = m_time.elapsed();
1057
    bool walked = false;
1058
1059
    const int stepSize = 5;
1060
    int steps = (elapsed - m_simulationTime) / stepSize;
1061
1062
    if (steps) {
1063
        m_deltaYaw /= steps;
1064
        m_deltaPitch /= steps;
1065
1066
        m_deltaYaw += m_turningSpeed;
1067
        m_deltaPitch += m_pitchSpeed;
1068
    }
1069
1070
    qreal walkingVelocity = m_walkingVelocity;
1071
    if (m_walkingItem->walking())
1072
        walkingVelocity = 0.005;
1073
1074
    for (int i = 0; i < steps; ++i) {
1075
        m_camera.setYaw(m_camera.yaw() + m_deltaYaw);
1076
        m_camera.setPitch(m_camera.pitch() + m_deltaPitch);
1077
1078
        bool walking = false;
1079
        if (walkingVelocity != 0) {
1080
            QPointF walkingDelta = QLineF::fromPolar(walkingVelocity, m_camera.yaw() - 90).p2();
1081
            QPointF pos = m_camera.pos();
1082
            if (tryMove(pos, walkingDelta)) {
1083
                walking = true;
1084
                m_camera.setPos(pos);
1085
            }
1086
        }
1087
1088
        if (m_strafingVelocity != 0) {
1089
            QPointF walkingDelta = QLineF::fromPolar(m_strafingVelocity, m_camera.yaw()).p2();
1090
            QPointF pos = m_camera.pos();
1091
            if (tryMove(pos, walkingDelta)) {
1092
                walking = true;
1093
                m_camera.setPos(pos);
1094
            }
1095
        }
1096
1097
        walked = walked || walking;
1098
1099
        if (walking)
1100
            m_walkTime += stepSize;
1101
        m_simulationTime += stepSize;
1102
1103
        foreach (Entity *entity, m_entities) {
1104
            if (entity->move(this))
1105
                movedEntities.insert(entity);
1106
        }
1107
    }
1108
1109
    m_camera.setTime(m_walkTime * 0.001);
1110
1111
    if (walked || m_deltaYaw != 0 || m_deltaPitch != 0) {
1112
        updateTransforms();
1113
    } else {
1114
        foreach (Entity *entity, movedEntities)
1115
            entity->updateTransform(m_camera);
1116
    }
1117
1118
    if (steps) {
1119
        m_deltaYaw = 0;
1120
        m_deltaPitch = 0;
1121
    }
1122
}
1123
1124
void MazeScene::toggleDoors()
1125
{
1126
    setFocusItem(0);
1127
1128
    if (m_doorAnimation->state() == QTimeLine::Running)
1129
        return;
1130
1131
    foreach (QPushButton *button, m_buttons) {
1132
        if (m_doorAnimation->direction() == QTimeLine::Forward)
1133
            button->setText("Close Sesame!");
1134
        else
1135
            button->setText("Open Sesame!");
1136
    }
1137
1138
    m_doorAnimation->toggleDirection();
1139
    m_doorAnimation->start();
1140
}
1141
1142
void MazeScene::moveDoors(qreal value)
1143
{
1144
    bool opaqueStatusChanged = false;
1145
    foreach (WallItem *item, m_doors) {
1146
        item->setAnimationTime(1 - value);
1147
1148
        bool shouldBeOpaque = qFuzzyCompare(value, 1);
1149
        if (item->isOpaque() != shouldBeOpaque) {
1150
            opaqueStatusChanged = true;
1151
            item->setOpaque(shouldBeOpaque);
1152
        }
1153
    }
1154
    if (opaqueStatusChanged)
1155
        updateTransforms();
1156
}
1157
1158
void MazeScene::toggleRenderer()
1159
{
1160
#ifndef QT_NO_OPENGL
1161
    if (views().size() == 0)
1162
        return;
1163
    QGraphicsView *view = views().at(0);
1164
    if (view) {
1165
        view->setViewport(
1166
                view->viewport()->inherits("QGLWidget")
1167
                ? new QWidget
1168
                : new QGLWidget(QGLFormat(QGL::SampleBuffers)));
1169
1170
        updateRenderer();
1171
    }
1172
#endif
1173
}
1174
1175
void MazeScene::updateRenderer()
1176
{
1177
    QGraphicsView *view = views().isEmpty() ? 0 : views().at(0);
1178
    bool accelerated = m_accelerated || view && view->viewport()->inherits("QGLWidget");
1179
    if (view) {
1180
        if (accelerated)
1181
            view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
1182
        else
1183
            view->setRenderHints(QPainter::Antialiasing);
1184
    }
1185
1186
    foreach (WallItem *item, m_walls)
1187
        item->updateLighting(m_lights, item->type() == 2);
1188
}