凸四角形同士の転送ルーチン(3)

凸四角形同士の転送ルーチンのソースです。


めちゃめちゃ長いので、続きを見る場合にはご注意を。

void VertexBlt(
    word* dst,                      // 転送先
    const Point* dstPoints_,        // 転送先の位置(4点)
    const Rectangle& dstClip,       // 転送先クリッピング情報
    int dp,                         // 転送先ピッチ
    byte* src,                      // 転送元
    const Point* srcPoints_,        // 転送元の位置(4点)
    const Rectangle& srcClip,       // 転送元クリッピング情報
    int sp,                         // 転送元ピッチ
    word* pal,                      // 転送元のパレット
)
{
    // 転送に関する情報。
    // dstY と dstDY が無いのは、dstY はループ中の y のことだし、
    // dstDY を基準に増分等を出しているので dstDY は必ず 1 になるから。
    struct VertexBltInfo
    {
        int     dstX;         // 転送先X の開始位置
        int     srcX;         // 転送元X の開始位置
        int     srcY;         // 転送元Y の開始位置
        int     dstDX;        // dstX の増分
        int     srcDX;        // srcX の増分
        int     srcDY;        // srcY の増分
    };
    int             ybase[4];
    VertexBltInfo   vertexBltInfo[4][2];

    // 初期設定

    Point dstPoints[4];
    Point srcPoints[4];
    // いろいろ操作するのでコピー
    for (int i = 0 ; i < 4 ; i++)
    {
        dstPoints[i] = dstPoints_[i];
        srcPoints[i] = srcPoints_[i];
    }
    // 転送先クリッピングに関する処理
    Size dstSize;
    int cl, cr, cu, cd;
    {
        // 転送先の最小矩形を求める
        int dx = min(min(min(dstPoints[0].x, dstPoints[1].x), dstPoints[2].x), dstPoints[3].x);
        int dy = min(min(min(dstPoints[0].y, dstPoints[1].y), dstPoints[2].y), dstPoints[3].y);
        dstSize.w = max(max(max(dstPoints[0].x, dstPoints[1].x), dstPoints[2].x), dstPoints[3].x) - dx + 1;
        dstSize.h = max(max(max(dstPoints[0].y, dstPoints[1].y), dstPoints[2].y), dstPoints[3].y) - dy + 1;

        // 完全に範囲外
        // ほんとは線分の交差判定もするべきなんだろうけど、めんどいのでパス
        if (dx + dstSize.w < dstClip.x ||
            dy + dstSize.h < dstClip.y ||
            dx >= dstClip.x + dstClip.w ||
            dy >= dstClip.y + dstClip.h)
        {
            return;
        }

        cl = cr = cu = cd = 0;

        // 左
        if (dx < dstClip.x)
        {
            cl = dstClip.x - dx;
        }
        // 右(これは広さではなく、右側の座標そのもの)
        if (dx > dstClip.x)
        {
            cr = dx - dstClip.x;
        }
        cr = cl + dstClip.x + dstClip.w - cr;

        // 上
        if (dy < dstClip.y)
        {
            cu = dstClip.y - dy;
        }
        // 下
        if (dy + dstSize.h > dstClip.y + dstClip.h)
        {
            cd = (dy + dstSize.h) - (dstClip.y + dstClip.h);
        }

        dst = (word*)((byte*)dst + dx * sizeof(word) + dy * dp);

        // dstPoints をずらす
        for (int i = 0; i < 4; i++)
        {
            dstPoints[i].x -= dx;
            dstPoints[i].y -= dy;
        }
    }
    // 転送元クリッピングに関する処理
    Size srcSize;
    bool bSrcClip;
    {
        // 転送先の最小矩形を求める
        int sx = min(min(min(srcPoints[0].x, srcPoints[1].x), srcPoints[2].x), srcPoints[3].x);
        int sy = min(min(min(srcPoints[0].y, srcPoints[1].y), srcPoints[2].y), srcPoints[3].y);
        srcSize.w = max(max(max(srcPoints[0].x, srcPoints[1].x), srcPoints[2].x), srcPoints[3].x) - sx + 1;
        srcSize.h = max(max(max(srcPoints[0].y, srcPoints[1].y), srcPoints[2].y), srcPoints[3].y) - sy + 1;

        // 完全に範囲外
        if (sx + srcSize.w < srcClip.x ||
            sy + srcSize.h < srcClip.y ||
            sx >= srcClip.x + srcClip.w ||
            sy >= srcClip.y + srcClip.h)
        {
            return;
        }

        // 転送元に対してクリッピングを行う必要があるか
        if (sx < srcClip.x ||
            sy < srcClip.y ||
            sx + srcSize.w > srcClip.x + srcClip.w ||
            sy + srcSize.h > srcClip.y + srcClip.h)
        {
            bSrcClip = true;
        }
        else
        {
            bSrcClip = false;
        }

        src = (byte*)((byte*)src + srcClip.x * sizeof(byte) + srcClip.y * sp);
        // srcPoints をずらす
        for (int i = 0; i < 4; i++)
        {
            srcPoints[i].x -= srcClip.x;
            srcPoints[i].y -= srcClip.y;
        }
    }

    // 16bit の固定小数で計算する
    for (int i = 0; i < 4; i++)
    {
        dstPoints[i].x <<= 16;
        dstPoints[i].y <<= 16;
        srcPoints[i].x <<= 16;
        srcPoints[i].y <<= 16;
    }

    // 描画情報を計算
    {
        // 次のラインの Y 座標情報
        int nextY[2];
        // 前回のラインのインデックス
        int line[2];
        // ラインの向き(順方向→1、逆方向→-1)
        int dir[2];
        // y == 0 の時点での初期情報を計算
        {
            // 最初は必ずゼロ
            ybase[0] = 0;

            int num = 0;
            int i;
            for (i = 0; i < 4; i++)
            {
                int n1, n2;
                n1 = i;
                n2 = (i + 1) & 3;
                Point p1 = dstPoints[n1];
                Point p2 = dstPoints[n2];
                Point s1 = srcPoints[n1];
                Point s2 = srcPoints[n2];
                // 線の繋がっている方向
                int d = 1;
                // X軸に平行
                if (p1.y == p2.y)
                {
                    continue;
                }
                if (p2.y == 0)
                {
                    // p1.y に 0 が来るようにする
                    swap(p1, p2);
                    swap(s1, s2);
                    swap(n1, n2);
                    // 逆方向
                    d = -1;
                }
                if (p1.y == 0)
                {
                    VertexBltInfo* info = &vertexBltInfo[0][num];
                    info->dstX = p1.x;
                    info->srcX = s1.x;
                    info->srcY = s1.y;
                    info->dstDX = (int)(((int64)(p2.x - p1.x) << 16) / p2.y);
                    info->srcDX = (int)(((int64)(s2.x - s1.x) << 16) / p2.y);
                    info->srcDY = (int)(((int64)(s2.y - s1.y) << 16) / p2.y);

                    line[num] = n1;
                    dir[num] = d;
                    nextY[num] = (p2.y >> 16);

                    // y == 0 地点で2点以上交差することって無いよね?
                    num++;
                    if (num == 2)
                    {
                        break;
                    }
                }
            }
            // 交点が見つからなかった→全ての点のY座標が同じ
            if (i == 4)
            {
                // ほんとうは1行のラインが描画されるべきなんだろうけど、めんどいからいいや……。
                return;
            }
        }
        int infoNum = 1;
        // 左右両方のラインが一番下までいけば終了
        while (nextY[0] != dstSize.h - 1 || nextY[1] != dstSize.h - 1)
        {
            int min, max;
            // Y の小さい方を見る
            if (nextY[0] <= nextY[1])
            {
                min = 0;
                max = 1;
            }
            else
            {
                min = 1;
                max = 0;
            }

            int n1, n2;
            Point p1, p2, s1, s2;

            n1 = (line[min] + dir[min]) & 3;
            n2 = (n1 + dir[min]) & 3;
            while (true)
            {
                p1 = dstPoints[n1];
                p2 = dstPoints[n2];
                // こうなってる(p1.y >= p2.y)時点で凸四角形じゃないんだけど、
                // とりあえずエラーは出ないようにしておく
                if (p1.y >= p2.y)
                {
                    n1 = (n1 + dir[min]) & 3;
                    n2 = (n1 + dir[min]) & 3;
                }
                else
                {
                    break;
                }
            }
            s1 = srcPoints[n1];
            s2 = srcPoints[n2];

            ybase[infoNum] = nextY[min];

            VertexBltInfo* info = &vertexBltInfo[infoNum][min];
            info->dstX = p1.x;
            info->srcX = s1.x;
            info->srcY = s1.y;
            info->dstDX = (int)(((int64)(p2.x - p1.x) << 16) / (p2.y - p1.y));
            info->srcDX = (int)(((int64)(s2.x - s1.x) << 16) / (p2.y - p1.y));
            info->srcDY = (int)(((int64)(s2.y - s1.y) << 16) / (p2.y - p1.y));
            // ◇ ←のような菱形の図形の場合、中央の2箇所の交点での Y 座標が同じになるので、
            // 反対側の情報も更新しなければならない
            if (nextY[min] == nextY[max])
            {
                line[min] = n1;
                // nextY[min] を次のラインの Y 座標に設定
                if (nextY[min] != dstSize.h - 1)
                {
                    nextY[min] = (dstPoints[n2].y >> 16);
                }

                // max 側の情報も更新
                {
                    n1 = (line[max] + dir[max]) & 3;
                    n2 = (n1 + dir[max]) & 3;
                    while (true)
                    {
                        p1 = dstPoints[n1];
                        p2 = dstPoints[n2];
                        if (p1.y >= p2.y)
                        {
                            n1 = (n1 + dir[max]) & 3;
                            n2 = (n1 + dir[max]) & 3;
                        }
                        else
                        {
                            break;
                        }
                    }
                    s1 = srcPoints[n1];
                    s2 = srcPoints[n2];

                    line[max] = n1;
                    if (nextY[max] != dstSize.h - 1)
                    {
                        nextY[max] = (dstPoints[n2].y >> 16);
                    }

                    info = &vertexBltInfo[infoNum][max];

                    info->dstX = p1.x;
                    info->srcX = s1.x;
                    info->srcY = s1.y;
                    info->dstDX = (int)(((int64)(p2.x - p1.x) << 16) / (p2.y - p1.y));
                    info->srcDX = (int)(((int64)(s2.x - s1.x) << 16) / (p2.y - p1.y));
                    info->srcDY = (int)(((int64)(s2.y - s1.y) << 16) / (p2.y - p1.y));
                }
            }
            else
            {
                line[min] = n1;
                // nextY[min] を次のラインの Y 座標に設定
                if (nextY[min] != dstSize.h - 1)
                {
                    nextY[min] = (dstPoints[n2].y >> 16);
                }

                // max 側は前の位置からの差分で動かしてやるだけ
                info = &vertexBltInfo[infoNum][max];
                VertexBltInfo* prevInfo = &vertexBltInfo[infoNum - 1][max];
                int height = ybase[infoNum] - ybase[infoNum - 1];

                info->dstX = prevInfo->dstX + prevInfo->dstDX * height;
                info->srcX = prevInfo->srcX + prevInfo->srcDX * height;
                info->srcY = prevInfo->srcY + prevInfo->srcDY * height;
                info->dstDX = prevInfo->dstDX;
                info->srcDX = prevInfo->srcDX;
                info->srcDY = prevInfo->srcDY;
            }
            infoNum++;
        }
        // 最後は dstSize.h より大きくないといけない
        ybase[infoNum] = dstSize.h;
    }


    VertexBltInfo* info1 = &vertexBltInfo[0][0];
    VertexBltInfo* info2 = &vertexBltInfo[0][1];
    int infoNum = 1;

    int y = 0;
    // Y座標クリッピング処理
    {
        while (ybase[infoNum] < cu)
        {
            y = ybase[infoNum];
            info1 = &vertexBltInfo[infoNum][0];
            info2 = &vertexBltInfo[infoNum][1];
            infoNum++;
        }
        int h = cu - y;
        info1->srcX += info1->srcDX * h;
        info1->srcY += info1->srcDY * h;
        info1->dstX += info1->dstDX * h;
        info2->srcX += info2->srcDX * h;
        info2->srcY += info2->srcDY * h;
        info2->dstX += info2->dstDX * h;
        dst = (word*)((byte*)dst + (dp * cu));
        y += h;

        dstSize.h -= cd;
    }
    // 転送開始
    for (; y < dstSize.h; y++)
    {
        // ybase の位置で転送情報を変える
        if (y == ybase[infoNum])
        {
            info1 = &vertexBltInfo[infoNum][0];
            info2 = &vertexBltInfo[infoNum][1];
            infoNum++;
        }
        // 正当な凸四角形であればループの外でこれを行ってもいいが、
        // 途中で交差してる四角形のためにここで swap してやる。
        if (info1->dstX > info2->dstX)
        {
            swap(info1, info2);
        }

        // X 方向の開始位置と増分を計算
        int s, e, SX, SY, SDX, SDY;
        {
            int frame = (info2->dstX - info1->dstX + (1 << 16));
            SDX = (int)(((int64)(info2->srcX - info1->srcX) << 16) / frame);
            SDY = (int)(((int64)(info2->srcY - info1->srcY) << 16) / frame);
        }
        SX = info1->srcX;
        SY = info1->srcY;
        s = info1->dstX >> 16;
        e = info2->dstX >> 16;

        // X座標クリッピング処理
        {
            if (s < cl)
            {
                int ccl = cl - s;
                SX += SDX * ccl;
                SY += SDY * ccl;
                s += ccl;
            }
            if (e >= cr)
            {
                e -= e - cr + 1;
            }
        }

        // 更に高速化を考えるのであれば Y ループの外側に出すべき。
        // BREW の場合は容量との兼ね合いもあるのでここで分岐させておく。
        if (!bSrcClip)
        {
            // X方向に転送開始(転送元クリッピング無し)
            for (; s <= e ;s++)
            {
                dst[s] = pal[src[(SY >> 16) * sp + (SX >> 16)]];
                SX += SDX;
                SY += SDY;
            }
        }
        else
        {
            // X方向に転送開始(転送元クリッピング有り)
            for (; s <= e; s++)
            {
                if ((uint32)(SX >> 16) < (uint32)srcClip.w && (uint32)(SY >> 16) < (uint32)srcClip.h)
                {
                    dst[s] = pal[src[(SY >> 16) * sp + (SX >> 16)]];
                }
                SX += SDX;
                SY += SDY;
            }
        }

        // 直線の増分を加算
        info1->srcX += info1->srcDX;
        info1->srcY += info1->srcDY;
        info1->dstX += info1->dstDX;
        info2->srcX += info2->srcDX;
        info2->srcY += info2->srcDY;
        info2->dstX += info2->dstDX;
        dst = (word*)((byte*)dst + dp);
    }
}

(;´ρ`)チカレタヨ・・・