Qt二維圖檔到3D場景應用
文章目錄
-
- Qt二維圖檔到3D場景應用
-
- 簡述
- 效果圖
- 相關代碼
- 結尾
簡述
在學習Qt提供的Demo中,發現一個很有意思的Demo,實作原理不是很懂。本人對此Demo非常感興趣,很想知道其原理實作。有沒有相關書籍介紹其算法,寫此篇文章的原因是,希望通過此篇文章 “抛磚引玉” , 歡迎各位留言一起交流學習下(也可以歡迎加入Qt交流大會,一起交流學習下:3246214072 )。好了,一起來看下效果圖。
效果圖
其原二維平面圖:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXxklaOdXV65UMBpXT1gnMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zMwEDOwADN2EDNxATM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
經過算法處理後得到三維空間效果:
相關代碼
RayCastingWidget::RayCastingWidget(QWidget *parent) :
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 RayCastingWidget::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 RayCastingWidget::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 RayCastingWidget::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) {
// every time 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));
}
void RayCastingWidget::resizeEvent(QResizeEvent *)
{
touchDevice = false;
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 RayCastingWidget::timerEvent(QTimerEvent *)
{
updatePlayer();
render();
showFps();
}
void RayCastingWidget::paintEvent(QPaintEvent *event)
{
QPainter p(this);
p.drawImage(event->rect(), buffer, event->rect() );
}
void RayCastingWidget::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 RayCastingWidget::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 RayCastingWidget::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 RayCastingWidget::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 RayCastingWidget::mouseReleaseEvent(QMouseEvent *)
{
angleDelta = 0;
moveDelta = 0;
}
結尾
各位大佬,留言講解一二