#include <QtCore>
#include <QtWidgets>
#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) {
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() {
if (buffer.size() != bufferSize)
buffer = QImage(bufferSize, QImage::Format_ARGB32);
int bufw = buffer.width();
int bufh = buffer.height();
if (bufw <= 0 || bufh <= 0)
return;
const uchar *ptr = buffer.bits();
QRgb *start = (QRgb*)(ptr);
QRgb stride = buffer.bytesPerLine() / 4;
QRgb *finish = start + stride * bufh;
const uchar *src = textureImg.bits();
const QRgb *texsrc = reinterpret_cast<const QRgb*>(src);
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) {
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;
qreal px = playerPos.x();
qreal py = playerPos.y();
int mapx = static_cast<int>(px);
int mapy = static_cast<int>(py);
int texture = 0;
qreal hitdist = 0.1;
qreal texofs = 0;
bool dark = false;
qreal distx = (u > 0) ? (mapx + 1 - px) * duu : (px - mapx) * duu;
qreal disty = (v > 0) ? (mapy + 1 - py) * dvv : (py - mapy) * dvv;
while (texture <= 0) {
if (distx > disty) {
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 {
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;
}
}
}
}
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;
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;
int y1 = bufh / 2;
int y2 = y1 + 1;
QRgb *pixel1 = start + y1 * stride + ray;
QRgb *pixel2 = pixel1 + stride;
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;
}
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;
#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");
w.resize(640, 480);
w.show();
return app.exec();
}