Update copyright headers
[qt:qt.git] / demos / embedded / raycasting / raycasting.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the demonstration applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QtCore>
43 #include <QtGui>
44
45 #include <math.h>
46
47 #ifndef M_PI
48 #define M_PI 3.14159265358979323846
49 #endif
50
51 #define WORLD_SIZE 8
52 int world_map[WORLD_SIZE][WORLD_SIZE] = {
53     { 1, 1, 1, 1, 6, 1, 1, 1 },
54     { 1, 0, 0, 1, 0, 0, 0, 7 },
55     { 1, 1, 0, 1, 0, 1, 1, 1 },
56     { 6, 0, 0, 0, 0, 0, 0, 3 },
57     { 1, 8, 8, 0, 8, 0, 8, 1 },
58     { 2, 2, 0, 0, 8, 8, 7, 1 },
59     { 3, 0, 0, 0, 0, 0, 0, 5 },
60     { 2, 2, 2, 2, 7, 4, 4, 4 },
61 };
62
63 #define TEXTURE_SIZE 64
64 #define TEXTURE_BLOCK (TEXTURE_SIZE * TEXTURE_SIZE)
65
66 class Raycasting: public QWidget
67 {
68 public:
69     Raycasting(QWidget *parent = 0)
70             : QWidget(parent)
71             , angle(0.5)
72             , playerPos(1.5, 1.5)
73             , angleDelta(0)
74             , moveDelta(0)
75             , touchDevice(false) {
76
77         // http://www.areyep.com/RIPandMCS-TextureLibrary.html
78         textureImg.load(":/textures.png");
79         textureImg = textureImg.convertToFormat(QImage::Format_ARGB32);
80         Q_ASSERT(textureImg.width() == TEXTURE_SIZE * 2);
81         Q_ASSERT(textureImg.bytesPerLine() == 4 * TEXTURE_SIZE * 2);
82         textureCount = textureImg.height() / TEXTURE_SIZE;
83
84         watch.start();
85         ticker.start(25, this);
86         setAttribute(Qt::WA_OpaquePaintEvent, true);
87         setMouseTracking(false);
88     }
89
90     void updatePlayer() {
91         int interval = qBound(20, watch.elapsed(), 250);
92         watch.start();
93         angle += angleDelta * interval / 1000;
94         qreal step = moveDelta * interval / 1000;
95         qreal dx = cos(angle) * step;
96         qreal dy = sin(angle) * step;
97         QPointF pos = playerPos + 3 * QPointF(dx, dy);
98         int xi = static_cast<int>(pos.x());
99         int yi = static_cast<int>(pos.y());
100         if (world_map[yi][xi] == 0)
101             playerPos = playerPos + QPointF(dx, dy);
102     }
103
104     void showFps() {
105         static QTime frameTick;
106         static int totalFrame = 0;
107         if (!(totalFrame & 31)) {
108             int elapsed = frameTick.elapsed();
109             frameTick.start();
110             int fps = 32 * 1000 / (1 + elapsed);
111             setWindowTitle(QString("Raycasting (%1 FPS)").arg(fps));
112         }
113         totalFrame++;
114     }
115
116     void render() {
117
118         // setup the screen surface
119         if (buffer.size() != bufferSize)
120             buffer = QImage(bufferSize, QImage::Format_ARGB32);
121         int bufw = buffer.width();
122         int bufh = buffer.height();
123         if (bufw <= 0 || bufh <= 0)
124             return;
125
126         // we intentionally cheat here, to avoid detach
127         const uchar *ptr = buffer.bits();
128         QRgb *start = (QRgb*)(ptr);
129         QRgb stride = buffer.bytesPerLine() / 4;
130         QRgb *finish = start + stride * bufh;
131
132         // prepare the texture pointer
133         const uchar *src = textureImg.bits();
134         const QRgb *texsrc = reinterpret_cast<const QRgb*>(src);
135
136         // cast all rays here
137         qreal sina = sin(angle);
138         qreal cosa = cos(angle);
139         qreal u = cosa - sina;
140         qreal v = sina + cosa;
141         qreal du = 2 * sina / bufw;
142         qreal dv = -2 * cosa / bufw;
143
144         for (int ray = 0; ray < bufw; ++ray, u += du, v += dv) {
145             // every time this ray advances 'u' units in x direction,
146             // it also advanced 'v' units in y direction
147             qreal uu = (u < 0) ? -u : u;
148             qreal vv = (v < 0) ? -v : v;
149             qreal duu = 1 / uu;
150             qreal dvv = 1 / vv;
151             int stepx = (u < 0) ? -1 : 1;
152             int stepy = (v < 0) ? -1 : 1;
153
154             // the cell in the map that we need to check
155             qreal px = playerPos.x();
156             qreal py = playerPos.y();
157             int mapx = static_cast<int>(px);
158             int mapy = static_cast<int>(py);
159
160             // the position and texture for the hit
161             int texture = 0;
162             qreal hitdist = 0.1;
163             qreal texofs = 0;
164             bool dark = false;
165
166             // first hit at constant x and constant y lines
167             qreal distx = (u > 0) ? (mapx + 1 - px) * duu : (px - mapx) * duu;
168             qreal disty = (v > 0) ? (mapy + 1 - py) * dvv : (py - mapy) * dvv;
169
170             // loop until we hit something
171             while (texture <= 0) {
172                 if (distx > disty) {
173                     // shorter distance to a hit in constant y line
174                     hitdist = disty;
175                     disty += dvv;
176                     mapy += stepy;
177                     texture = world_map[mapy][mapx];
178                     if (texture > 0) {
179                         dark = true;
180                         if (stepy > 0) {
181                             qreal ofs = px + u * (mapy - py) / v;
182                             texofs = ofs - floor(ofs);
183                         } else {
184                             qreal ofs = px + u * (mapy + 1 - py) / v;
185                             texofs = ofs - floor(ofs);
186                         }
187                     }
188                 } else {
189                     // shorter distance to a hit in constant x line
190                     hitdist = distx;
191                     distx += duu;
192                     mapx += stepx;
193                     texture = world_map[mapy][mapx];
194                     if (texture > 0) {
195                         if (stepx > 0) {
196                             qreal ofs = py + v * (mapx - px) / u;
197                             texofs = ofs - floor(ofs);
198                         } else {
199                             qreal ofs = py + v * (mapx + 1 - px) / u;
200                             texofs = ceil(ofs) - ofs;
201                         }
202                     }
203                 }
204             }
205
206             // get the texture, note that the texture image
207             // has two textures horizontally, "normal" vs "dark"
208             int col = static_cast<int>(texofs * TEXTURE_SIZE);
209             col = qBound(0, col, TEXTURE_SIZE - 1);
210             texture = (texture - 1) % textureCount;
211             const QRgb *tex = texsrc + TEXTURE_BLOCK * texture * 2 +
212                               (TEXTURE_SIZE * 2 * col);
213             if (dark)
214                 tex += TEXTURE_SIZE;
215
216             // start from the texture center (horizontally)
217             int h = static_cast<int>(bufw / hitdist / 2);
218             int dy = (TEXTURE_SIZE << 12) / h;
219             int p1 = ((TEXTURE_SIZE / 2) << 12) - dy;
220             int p2 = p1 + dy;
221
222             // start from the screen center (vertically)
223             // y1 will go up (decrease), y2 will go down (increase)
224             int y1 = bufh / 2;
225             int y2 = y1 + 1;
226             QRgb *pixel1 = start + y1 * stride + ray;
227             QRgb *pixel2 = pixel1 + stride;
228
229             // map the texture to the sliver
230             while (y1 >= 0 && y2 < bufh && p1 >= 0) {
231                 *pixel1 = tex[p1 >> 12];
232                 *pixel2 = tex[p2 >> 12];
233                 p1 -= dy;
234                 p2 += dy;
235                 --y1;
236                 ++y2;
237                 pixel1 -= stride;
238                 pixel2 += stride;
239             }
240
241             // ceiling and floor
242             for (; pixel1 > start; pixel1 -= stride)
243                 *pixel1 = qRgb(0, 0, 0);
244             for (; pixel2 < finish; pixel2 += stride)
245                 *pixel2 = qRgb(96, 96, 96);
246         }
247
248         update(QRect(QPoint(0, 0), bufferSize));
249     }
250
251 protected:
252
253     void resizeEvent(QResizeEvent*) {
254 #if defined(Q_OS_WINCE_WM)
255         touchDevice = true;
256 #elif defined(Q_OS_SYMBIAN)
257         // FIXME: use HAL
258         if (width() > 480 || height() > 480)
259             touchDevice = true;
260 #else
261         touchDevice = false;
262 #endif
263         if (touchDevice) {
264             if (width() < height()) {
265                 trackPad = QRect(0, height() / 2, width(), height() / 2);
266                 centerPad = QPoint(width() / 2, height() * 3 / 4);
267                 bufferSize = QSize(width(), height() / 2);
268             } else {
269                 trackPad = QRect(width() / 2, 0, width() / 2, height());
270                 centerPad = QPoint(width() * 3 / 4, height() / 2);
271                 bufferSize = QSize(width() / 2, height());
272             }
273         } else {
274             trackPad = QRect();
275             bufferSize = size();
276         }
277         update();
278    }
279
280     void timerEvent(QTimerEvent*) {
281         updatePlayer();
282         render();
283         showFps();
284     }
285
286     void paintEvent(QPaintEvent *event) {
287         QPainter p(this);
288         p.setCompositionMode(QPainter::CompositionMode_Source);
289
290         p.drawImage(event->rect(), buffer, event->rect());
291
292         if (touchDevice && event->rect().intersects(trackPad)) {
293             p.fillRect(trackPad, Qt::white);
294             p.setPen(QPen(QColor(224, 224, 224), 6));
295             int rad = qMin(trackPad.width(), trackPad.height()) * 0.3;
296             p.drawEllipse(centerPad, rad, rad);
297
298             p.setPen(Qt::NoPen);
299             p.setBrush(Qt::gray);
300
301             QPolygon poly;
302             poly << QPoint(-30, 0);
303             poly << QPoint(0, -40);
304             poly << QPoint(30, 0);
305
306             p.translate(centerPad);
307             for (int i = 0; i < 4; ++i) {
308                 p.rotate(90);
309                 p.translate(0, 20 - rad);
310                 p.drawPolygon(poly);
311                 p.translate(0, rad - 20);
312             }
313         }
314
315         p.end();
316     }
317
318     void keyPressEvent(QKeyEvent *event) {
319         event->accept();
320         if (event->key() == Qt::Key_Left)
321             angleDelta = 1.3 * M_PI;
322         if (event->key() == Qt::Key_Right)
323             angleDelta = -1.3 * M_PI;
324         if (event->key() == Qt::Key_Up)
325             moveDelta = 2.5;
326         if (event->key() == Qt::Key_Down)
327             moveDelta = -2.5;
328     }
329
330     void keyReleaseEvent(QKeyEvent *event) {
331         event->accept();
332         if (event->key() == Qt::Key_Left)
333             angleDelta = (angleDelta > 0) ? 0 : angleDelta;
334         if (event->key() == Qt::Key_Right)
335             angleDelta = (angleDelta < 0) ? 0 : angleDelta;
336         if (event->key() == Qt::Key_Up)
337             moveDelta = (moveDelta > 0) ? 0 : moveDelta;
338         if (event->key() == Qt::Key_Down)
339             moveDelta = (moveDelta < 0) ? 0 : moveDelta;
340     }
341
342     void mousePressEvent(QMouseEvent *event) {
343         qreal dx = centerPad.x() - event->pos().x();
344         qreal dy = centerPad.y() - event->pos().y();
345         angleDelta = dx * 2 * M_PI / width();
346         moveDelta = dy * 10 / height();
347     }
348
349     void mouseMoveEvent(QMouseEvent *event) {
350         qreal dx = centerPad.x() - event->pos().x();
351         qreal dy = centerPad.y() - event->pos().y();
352         angleDelta = dx * 2 * M_PI / width();
353         moveDelta = dy * 10 / height();
354     }
355
356     void mouseReleaseEvent(QMouseEvent*) {
357         angleDelta = 0;
358         moveDelta = 0;
359     }
360
361 private:
362     QTime watch;
363     QBasicTimer ticker;
364     QImage buffer;
365     qreal angle;
366     QPointF playerPos;
367     qreal angleDelta;
368     qreal moveDelta;
369     QImage textureImg;
370     int textureCount;
371     bool touchDevice;
372     QRect trackPad;
373     QPoint centerPad;
374     QSize bufferSize;
375 };
376
377 int main(int argc, char **argv)
378 {
379     QApplication app(argc, argv);
380
381     Raycasting w;
382     w.setWindowTitle("Raycasting");
383 #if defined(Q_OS_SYMBIAN) || defined(Q_OS_WINCE_WM)
384     w.showMaximized();
385 #else
386     w.resize(640, 480);
387     w.show();
388 #endif
389
390     return app.exec();
391 }