tests/qtp/qtp_raycasting/raycasting.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qtp/qtp_raycasting/raycasting.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,391 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore>
+#include <QtGui>
+
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define WORLD_SIZE 8
+int world_map[WORLD_SIZE][WORLD_SIZE] = {
+    { 1, 1, 1, 1, 6, 1, 1, 1 },
+    { 1, 0, 0, 1, 0, 0, 0, 7 },
+    { 1, 1, 0, 1, 0, 1, 1, 1 },
+    { 6, 0, 0, 0, 0, 0, 0, 3 },
+    { 1, 8, 8, 0, 8, 0, 8, 1 },
+    { 2, 2, 0, 0, 8, 8, 7, 1 },
+    { 3, 0, 0, 0, 0, 0, 0, 5 },
+    { 2, 2, 2, 2, 7, 4, 4, 4 },
+};
+
+#define TEXTURE_SIZE 64
+#define TEXTURE_BLOCK (TEXTURE_SIZE * TEXTURE_SIZE)
+
+class Raycasting: public QWidget
+{
+public:
+    Raycasting(QWidget *parent = 0)
+            : QWidget(parent)
+            , angle(0.5)
+            , playerPos(1.5, 1.5)
+            , angleDelta(0)
+            , moveDelta(0)
+            , touchDevice(false) {
+
+        // http://www.areyep.com/RIPandMCS-TextureLibrary.html
+        textureImg.load(":/textures.png");
+        textureImg = textureImg.convertToFormat(QImage::Format_ARGB32);
+        Q_ASSERT(textureImg.width() == TEXTURE_SIZE * 2);
+        Q_ASSERT(textureImg.bytesPerLine() == 4 * TEXTURE_SIZE * 2);
+        textureCount = textureImg.height() / TEXTURE_SIZE;
+
+        watch.start();
+        ticker.start(25, this);
+        setAttribute(Qt::WA_OpaquePaintEvent, true);
+        setMouseTracking(false);
+    }
+
+    void updatePlayer() {
+        int interval = qBound(20, watch.elapsed(), 250);
+        watch.start();
+        angle += angleDelta * interval / 1000;
+        qreal step = moveDelta * interval / 1000;
+        qreal dx = cos(angle) * step;
+        qreal dy = sin(angle) * step;
+        QPointF pos = playerPos + 3 * QPointF(dx, dy);
+        int xi = static_cast<int>(pos.x());
+        int yi = static_cast<int>(pos.y());
+        if (world_map[yi][xi] == 0)
+            playerPos = playerPos + QPointF(dx, dy);
+    }
+
+    void showFps() {
+        static QTime frameTick;
+        static int totalFrame = 0;
+        if (!(totalFrame & 31)) {
+            int elapsed = frameTick.elapsed();
+            frameTick.start();
+            int fps = 32 * 1000 / (1 + elapsed);
+            setWindowTitle(QString("Raycasting (%1 FPS)").arg(fps));
+        }
+        totalFrame++;
+    }
+
+    void render() {
+
+        // setup the screen surface
+        if (buffer.size() != bufferSize)
+            buffer = QImage(bufferSize, QImage::Format_ARGB32);
+        int bufw = buffer.width();
+        int bufh = buffer.height();
+        if (bufw <= 0 || bufh <= 0)
+            return;
+
+        // we intentionally cheat here, to avoid detach
+        const uchar *ptr = buffer.bits();
+        QRgb *start = (QRgb*)(ptr);
+        QRgb stride = buffer.bytesPerLine() / 4;
+        QRgb *finish = start + stride * bufh;
+
+        // prepare the texture pointer
+        const uchar *src = textureImg.bits();
+        const QRgb *texsrc = reinterpret_cast<const QRgb*>(src);
+
+        // cast all rays here
+        qreal sina = sin(angle);
+        qreal cosa = cos(angle);
+        qreal u = cosa - sina;
+        qreal v = sina + cosa;
+        qreal du = 2 * sina / bufw;
+        qreal dv = -2 * cosa / bufw;
+
+        for (int ray = 0; ray < bufw; ++ray, u += du, v += dv) {
+            // everytime this ray advances 'u' units in x direction,
+            // it also advanced 'v' units in y direction
+            qreal uu = (u < 0) ? -u : u;
+            qreal vv = (v < 0) ? -v : v;
+            qreal duu = 1 / uu;
+            qreal dvv = 1 / vv;
+            int stepx = (u < 0) ? -1 : 1;
+            int stepy = (v < 0) ? -1 : 1;
+
+            // the cell in the map that we need to check
+            qreal px = playerPos.x();
+            qreal py = playerPos.y();
+            int mapx = static_cast<int>(px);
+            int mapy = static_cast<int>(py);
+
+            // the position and texture for the hit
+            int texture = 0;
+            qreal hitdist = 0.1;
+            qreal texofs = 0;
+            bool dark = false;
+
+            // first hit at constant x and constant y lines
+            qreal distx = (u > 0) ? (mapx + 1 - px) * duu : (px - mapx) * duu;
+            qreal disty = (v > 0) ? (mapy + 1 - py) * dvv : (py - mapy) * dvv;
+
+            // loop until we hit something
+            while (texture <= 0) {
+                if (distx > disty) {
+                    // shorter distance to a hit in constant y line
+                    hitdist = disty;
+                    disty += dvv;
+                    mapy += stepy;
+                    texture = world_map[mapy][mapx];
+                    if (texture > 0) {
+                        dark = true;
+                        if (stepy > 0) {
+                            qreal ofs = px + u * (mapy - py) / v;
+                            texofs = ofs - floor(ofs);
+                        } else {
+                            qreal ofs = px + u * (mapy + 1 - py) / v;
+                            texofs = ofs - floor(ofs);
+                        }
+                    }
+                } else {
+                    // shorter distance to a hit in constant x line
+                    hitdist = distx;
+                    distx += duu;
+                    mapx += stepx;
+                    texture = world_map[mapy][mapx];
+                    if (texture > 0) {
+                        if (stepx > 0) {
+                            qreal ofs = py + v * (mapx - px) / u;
+                            texofs = ofs - floor(ofs);
+                        } else {
+                            qreal ofs = py + v * (mapx + 1 - px) / u;
+                            texofs = ceil(ofs) - ofs;
+                        }
+                    }
+                }
+            }
+
+            // get the texture, note that the texture image
+            // has two textures horizontally, "normal" vs "dark"
+            int col = static_cast<int>(texofs * TEXTURE_SIZE);
+            col = qBound(0, col, TEXTURE_SIZE - 1);
+            texture = (texture - 1) % textureCount;
+            const QRgb *tex = texsrc + TEXTURE_BLOCK * texture * 2 +
+                              (TEXTURE_SIZE * 2 * col);
+            if (dark)
+                tex += TEXTURE_SIZE;
+
+            // start from the texture center (horizontally)
+            int h = static_cast<int>(bufw / hitdist / 2);
+            int dy = (TEXTURE_SIZE << 12) / h;
+            int p1 = ((TEXTURE_SIZE / 2) << 12) - dy;
+            int p2 = p1 + dy;
+
+            // start from the screen center (vertically)
+            // y1 will go up (decrease), y2 will go down (increase)
+            int y1 = bufh / 2;
+            int y2 = y1 + 1;
+            QRgb *pixel1 = start + y1 * stride + ray;
+            QRgb *pixel2 = pixel1 + stride;
+
+            // map the texture to the sliver
+            while (y1 >= 0 && y2 < bufh && p1 >= 0) {
+                *pixel1 = tex[p1 >> 12];
+                *pixel2 = tex[p2 >> 12];
+                p1 -= dy;
+                p2 += dy;
+                --y1;
+                ++y2;
+                pixel1 -= stride;
+                pixel2 += stride;
+            }
+
+            // ceiling and floor
+            for (; pixel1 > start; pixel1 -= stride)
+                *pixel1 = qRgb(0, 0, 0);
+            for (; pixel2 < finish; pixel2 += stride)
+                *pixel2 = qRgb(96, 96, 96);
+        }
+
+        update(QRect(QPoint(0, 0), bufferSize));
+    }
+
+protected:
+
+    void resizeEvent(QResizeEvent*) {
+#if defined(Q_OS_WINCE_WM)
+        touchDevice = true;
+#elif defined(Q_OS_SYMBIAN)
+        // FIXME: use HAL
+        if (width() > 480 || height() > 480)
+            touchDevice = true;
+#else
+        touchDevice = false;
+#endif
+        if (touchDevice) {
+            if (width() < height()) {
+                trackPad = QRect(0, height() / 2, width(), height() / 2);
+                centerPad = QPoint(width() / 2, height() * 3 / 4);
+                bufferSize = QSize(width(), height() / 2);
+            } else {
+                trackPad = QRect(width() / 2, 0, width() / 2, height());
+                centerPad = QPoint(width() * 3 / 4, height() / 2);
+                bufferSize = QSize(width() / 2, height());
+            }
+        } else {
+            trackPad = QRect();
+            bufferSize = size();
+        }
+        update();
+   }
+
+    void timerEvent(QTimerEvent*) {
+        updatePlayer();
+        render();
+        showFps();
+    }
+
+    void paintEvent(QPaintEvent *event) {
+        QPainter p(this);
+        p.setCompositionMode(QPainter::CompositionMode_Source);
+
+        p.drawImage(event->rect(), buffer, event->rect());
+
+        if (touchDevice && event->rect().intersects(trackPad)) {
+            p.fillRect(trackPad, Qt::white);
+            p.setPen(QPen(QColor(224, 224, 224), 6));
+            int rad = qMin(trackPad.width(), trackPad.height()) * 0.3;
+            p.drawEllipse(centerPad, rad, rad);
+
+            p.setPen(Qt::NoPen);
+            p.setBrush(Qt::gray);
+
+            QPolygon poly;
+            poly << QPoint(-30, 0);
+            poly << QPoint(0, -40);
+            poly << QPoint(30, 0);
+
+            p.translate(centerPad);
+            for (int i = 0; i < 4; ++i) {
+                p.rotate(90);
+                p.translate(0, 20 - rad);
+                p.drawPolygon(poly);
+                p.translate(0, rad - 20);
+            }
+        }
+
+        p.end();
+    }
+
+    void keyPressEvent(QKeyEvent *event) {
+        event->accept();
+        if (event->key() == Qt::Key_Left)
+            angleDelta = 1.3 * M_PI;
+        if (event->key() == Qt::Key_Right)
+            angleDelta = -1.3 * M_PI;
+        if (event->key() == Qt::Key_Up)
+            moveDelta = 2.5;
+        if (event->key() == Qt::Key_Down)
+            moveDelta = -2.5;
+    }
+
+    void keyReleaseEvent(QKeyEvent *event) {
+        event->accept();
+        if (event->key() == Qt::Key_Left)
+            angleDelta = (angleDelta > 0) ? 0 : angleDelta;
+        if (event->key() == Qt::Key_Right)
+            angleDelta = (angleDelta < 0) ? 0 : angleDelta;
+        if (event->key() == Qt::Key_Up)
+            moveDelta = (moveDelta > 0) ? 0 : moveDelta;
+        if (event->key() == Qt::Key_Down)
+            moveDelta = (moveDelta < 0) ? 0 : moveDelta;
+    }
+
+    void mousePressEvent(QMouseEvent *event) {
+        qreal dx = centerPad.x() - event->pos().x();
+        qreal dy = centerPad.y() - event->pos().y();
+        angleDelta = dx * 2 * M_PI / width();
+        moveDelta = dy * 10 / height();
+    }
+
+    void mouseMoveEvent(QMouseEvent *event) {
+        qreal dx = centerPad.x() - event->pos().x();
+        qreal dy = centerPad.y() - event->pos().y();
+        angleDelta = dx * 2 * M_PI / width();
+        moveDelta = dy * 10 / height();
+    }
+
+    void mouseReleaseEvent(QMouseEvent*) {
+        angleDelta = 0;
+        moveDelta = 0;
+    }
+
+private:
+    QTime watch;
+    QBasicTimer ticker;
+    QImage buffer;
+    qreal angle;
+    QPointF playerPos;
+    qreal angleDelta;
+    qreal moveDelta;
+    QImage textureImg;
+    int textureCount;
+    bool touchDevice;
+    QRect trackPad;
+    QPoint centerPad;
+    QSize bufferSize;
+};
+
+int main(int argc, char **argv)
+{
+    QApplication app(argc, argv);
+
+    Raycasting w;
+    w.setWindowTitle("Raycasting");
+#if defined(Q_OS_SYMBIAN) || defined(Q_OS_WINCE_WM)
+    w.showMaximized();
+#else
+    w.resize(640, 480);
+    w.show();
+#endif
+
+    return app.exec();
+}