source: trunk/demos/embedded/raycasting/raycasting.cpp@ 814

Last change on this file since 814 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

  • Property svn:eol-style set to native
File size: 12.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the demonstration applications of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
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
52int 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
66class Raycasting: public QWidget
67{
68public:
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 // everytime 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
251protected:
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
361private:
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
377int 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}
Note: See TracBrowser for help on using the repository browser.