Qt二维图片到3D场景应用

Qt二维图片到3D场景应用

简述

在学习Qt提供的Demo中,发现一个很有意思的Demo,实现原理不是很懂。本人对此Demo非常感兴趣,很想知道其原理实现。有没有相关书籍介绍其算法,写此篇文章的原因是,希望通过此篇文章 “抛砖引玉” , 欢迎各位留言一起交流学习下(也可以欢迎加入Qt交流大会,一起交流学习下:3246214072 )。好了,一起来看下效果图。

效果图

其原二维平面图:
在这里插入图片描述

经过算法处理后得到三维空间效果:
在这里插入图片描述

在这里插入图片描述

相关代码

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;
}

结尾

各位大佬,留言讲解一二

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨田哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值