0
|
1 |
/****************************************************************************
|
|
2 |
**
|
|
3 |
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
|
4 |
** All rights reserved.
|
|
5 |
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
6 |
**
|
|
7 |
** This file is part of the demonstration applications of the Qt Toolkit.
|
|
8 |
**
|
|
9 |
** $QT_BEGIN_LICENSE:LGPL$
|
|
10 |
** No Commercial Usage
|
|
11 |
** This file contains pre-release code and may not be distributed.
|
|
12 |
** You may use this file in accordance with the terms and conditions
|
|
13 |
** contained in the Technology Preview License Agreement accompanying
|
|
14 |
** this package.
|
|
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 |
** If you have questions regarding the use of this file, please contact
|
|
29 |
** Nokia at qt-info@nokia.com.
|
|
30 |
**
|
|
31 |
**
|
|
32 |
**
|
|
33 |
**
|
|
34 |
**
|
|
35 |
**
|
|
36 |
**
|
|
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 |
// 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 |
|
|
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 |
}
|