天天看點

Qt二維圖檔到3D場景應用

Qt二維圖檔到3D場景應用

文章目錄

    • Qt二維圖檔到3D場景應用
      • 簡述
      • 效果圖
      • 相關代碼
      • 結尾

簡述

在學習Qt提供的Demo中,發現一個很有意思的Demo,實作原理不是很懂。本人對此Demo非常感興趣,很想知道其原理實作。有沒有相關書籍介紹其算法,寫此篇文章的原因是,希望通過此篇文章 “抛磚引玉” , 歡迎各位留言一起交流學習下(也可以歡迎加入Qt交流大會,一起交流學習下:3246214072 )。好了,一起來看下效果圖。

效果圖

其原二維平面圖:

Qt二維圖檔到3D場景應用

經過算法處理後得到三維空間效果:

Qt二維圖檔到3D場景應用
Qt二維圖檔到3D場景應用

相關代碼

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

結尾

各位大佬,留言講解一二

繼續閱讀