レイキャスティングを使用して2Dグリッドに見通し線を実装するより効率的な方法は?


9

タイルの2Dグリッドと、見通し線を表す、プレーヤーの中心にある座標の近似球を考えます。目標は、障害物(つまり、壁)を超えた視線を遮断することです。

視野内の個々のセルが表示されているかどうかを判断するのは比較的簡単です。ブレゼンハムを使用して、プレーヤーからターゲットセルに光線をキャストします。プレーヤーとターゲットの間で重なっているセルの1つが障害物である場合、ターゲットセル見えません。

さて、私の最初の考えは、見通し内のすべてのグリッドセルを反復処理することでしたが、これは私には非効率的です。たとえば、プレーヤーが壁の隣に立っていて、壁の向こう側のセルが表示されていないと判断した場合、それ以降はレイのすべてのセルが非表示になると判断できます。

また、視野範囲の周囲に沿って各セルにレイをキャストし、各レイに沿って各セルを反復することも検討しましたが、一部のセルを複数回処理することになります。

これを行うより効率的な方法はありますか?

1ターンあたり50セルまで反復することは比較的軽量な計算ですが、私はスピードを求めています。目標は、自動再生で毎秒数ターンを循環できるようにすることです。だから、私がこれをもっと効率的にできるほど、より良いです。


「最良の」質問は通常はうまくいきません。以来最良の方法はあなたの目標とあなたがサポートする必要がある他の機能に非常に具体的です。コードをプロファイリングして、ニーズに十分対応できるかどうかを確認することをお勧めします。プロファイリングでは、パフォーマンスを向上させるために最初に改善する必要があるコードの部分も表示されます。
MichaelHouse

プレイヤーの周りにいくつの細胞が入ると予想しますか?
Luis Estrada

@Luisはおそらく7または8セルの半径です。
CodeMoose 2013年

2
gamedev.stackexchange.com/a/47560/4129を思い出してください。O (n)スイープで実行できます。
ウィル

2
最適化する必要がありますか?対処する必要があるボトルネックに実際に遭遇しましたか?それとも、それが将来の問題になると単純に推測していますか?コードが比較的モジュール化されている場合、ソリューションを開発し、後で最適化が必要な場合にそれを再利用することは、世界で最も簡単なことです。
ジェントルマン2013年

回答:


8

「シャドウアーク」をキャストして、一度に広い領域をカバーすることもできます。実際の詳細は少し複雑ですが、Eric Lippertがhttp://blogs.msdn.com/b/ericlippert/archive/2011/12/12/shadowcasting-inで(ライブSilverlightデモを使用して)非常に詳細な説明をしています。-c-part-one.aspx


ブログのリンクは死んでいる。この回答の更新はありますか?
ネオンワージ2018

これが、外部リンクに完全に依存するのではなく、回答が提案する手法の大まかな要約を含めることをお勧めしている理由です。この場合、@ NeonWargeは、後の回答でのStoikoのこの手法の実装は有用なガイドですか?
DMGregory

5

ジミーが提案したアルゴリズムを実装しました。

ここで動作中のコードのビデオ:https : //youtu.be/lIlPfwlcbHo

グリッド視野

/*
   What this code does:
      Rasterizes a single Field Of View octant on a grid, similar to the way 
      FOV / shadowcasting is implemented in some roguelikes.
      Clips to bitmap
      Steps on pixel centers
      Optional attenuation
      Optional circle clip
      Optional lit blocking tiles

   To rasterize the entire FOV, call this in a loop with octant in range 0-7
   Inspired by http://blogs.msdn.com/b/ericlippert/archive/2011/12/12/shadowcasting-in-c-part-one.aspx
*/

static inline int Mini( int a, int b ) {
    return a < b ? a : b;
}

static inline int Maxi( int a, int b ) {
    return a > b ? a : b;
}

static inline int Clampi( int v, int min, int max ) {
    return Maxi( min, Mini( v, max ) );
}

typedef union c2_s {
    struct {
        int x, y;
    };
    int a[2];
} c2_t;

static const c2_t c2zero = { .a = { 0, 0 } };
static const c2_t c2one = { .a = { 1, 1 } };

static inline c2_t c2xy( int x, int y ) {
    c2_t c = { { x, y } };
    return c;
}

static inline c2_t c2Neg( c2_t c ) {
    return c2xy( -c.x, -c.y );
}

static inline c2_t c2Add( c2_t a, c2_t b ) {
    return c2xy( a.x + b.x, a.y + b.y );
}

static inline c2_t c2Sub( c2_t a, c2_t b ) {
    return c2xy( a.x - b.x, a.y - b.y );
}

static inline int c2Dot( c2_t a, c2_t b ) {
    return a.x * b.x + a.y * b.y;
}

static inline int c2CrossC( c2_t a, c2_t b ) {
    return a.x * b.y - a.y * b.x;
}

static inline c2_t c2Clamp( c2_t c, c2_t min, c2_t max ) {
    return c2xy( Clampi( c.x, min.x, max.x ), Clampi( c.y, min.y, max.y ) );
}

static inline c2_t c2Scale( c2_t a, int s ) {
    return c2xy( a.x * s, a.y * s );
}

void RasterizeFOVOctant( int originX, int originY,
                         int radius, 
                         int bitmapWidth, int bitmapHeight,
                         int octant,
                         int skipAttenuation,
                         int skipClampToRadius,
                         int darkWalls,
                         const unsigned char *inBitmap, 
                         unsigned char *outBitmap ) {
#define READ_PIXEL(c) inBitmap[(c).x+(c).y*bitmapWidth]
#define WRITE_PIXEL(c,color) outBitmap[(c).x+(c).y*bitmapWidth]=(color)
#define MAX_RAYS 64
#define ADD_RAY(c) {nextRays->rays[Mini(nextRays->numRays,MAX_RAYS-1)] = (c);nextRays->numRays++;}
#define IS_ON_MAP(c) ((c).x >= 0 && (c).x < bitmapWidth && (c).y >= 0 && (c).y < bitmapHeight)
    typedef struct {
        int numRays;
        c2_t rays[MAX_RAYS];
    } raysList_t;
    // keep these coupled like this
    static const const c2_t bases[] = {
        { { 1, 0  } }, { { 0, 1  } },
        { { 1, 0  } }, { { 0, -1 } },
        { { -1, 0 } }, { { 0, -1 } },
        { { -1, 0 } }, { { 0, 1  } },
        { { 0, 1  } }, { { -1, 0 } },
        { { 0, 1  } }, { { 1, 0  } },
        { { 0, -1 } }, { { 1, 0  } },
        { { 0, -1 } }, { { -1, 0 } },
    }; 
    c2_t e0 = bases[( octant * 2 + 0 ) & 15];
    c2_t e1 = bases[( octant * 2 + 1 ) & 15];
    raysList_t rayLists[2] = { {
        .numRays = 2,
        .rays = {
            c2xy( 1, 0 ),
            c2xy( 1, 1 ),
        }, 
    } };
    c2_t bitmapSize = c2xy( bitmapWidth, bitmapHeight );
    c2_t bitmapMax = c2Sub( bitmapSize, c2one );
    c2_t origin = c2Clamp( c2xy( originX, originY ), c2zero, bitmapMax );
    if ( READ_PIXEL( origin ) ) {
        WRITE_PIXEL( origin, 255 );
        return;
    }
    c2_t dmin = c2Neg( origin );
    c2_t dmax = c2Sub( bitmapMax, origin );
    int dmin0 = c2Dot( dmin, e0 );
    int dmax0 = c2Dot( dmax, e0 );
    int limit0 = Mini( radius, dmin0 > 0 ? dmin0 : dmax0 );
    int dmin1 = c2Dot( dmin, e1 );
    int dmax1 = c2Dot( dmax, e1 );
    int limit1 = Mini( radius, dmin1 > 0 ? dmin1 : dmax1 );
    c2_t ci = origin;
    for ( int i = 0; i <= limit0; i++ ) {
        int i2 = i * 2;
        raysList_t *currRays = &rayLists[( i + 0 ) & 1];
        raysList_t *nextRays = &rayLists[( i + 1 ) & 1];
        nextRays->numRays = 0;
        for ( int r = 0; r < currRays->numRays - 1; r += 2 ) {
            c2_t r0 = currRays->rays[r + 0];
            c2_t r1 = currRays->rays[r + 1];
            int inyr0 = ( i2 - 1 ) * r0.y / r0.x;
            int outyr0 = ( i2 + 1 ) * r0.y / r0.x;
            int inyr1 = ( i2 - 1 ) * r1.y / r1.x;
            int outyr1 = ( i2 + 1 ) * r1.y / r1.x;

            // every pixel with a center INSIDE the frustum is lit

            int starty = outyr0 + 1;
            if ( c2CrossC( r0, c2xy( i2, outyr0 ) ) < 0 ) {
                starty++;
            }
            starty /= 2;
            c2_t start = c2Add( ci, c2Scale( e1, starty ) );
            int endy = inyr1 + 1;
            if ( c2CrossC( r1, c2xy( i2, inyr1 + 1 ) ) > 0 ) {
                endy--;
            }
            endy /= 2;
            //c2_t end = c2Add( ci, c2Scale( e1, endy ) );
            {
                int y;
                c2_t p;
                int miny = starty;
                int maxy = Mini( endy, limit1 ); 
                for ( y = miny, p = start; y <= maxy; y++, p = c2Add( p, e1 ) ) {
                    WRITE_PIXEL( p, 255 );
                }
            }

            // push rays for the next column

            // correct the bounds first

            c2_t bounds0;
            c2_t bounds1;
            c2_t firstin = c2Add( ci, c2Scale( e1, ( inyr0 + 1 ) / 2 ) );
            c2_t firstout = c2Add( ci, c2Scale( e1, ( outyr0 + 1 ) / 2 ) );
            if ( ( IS_ON_MAP( firstin ) && ! READ_PIXEL( firstin ) )
                && ( IS_ON_MAP( firstout ) && ! READ_PIXEL( firstout ) ) ) {
                  bounds0 = r0;
            } else {
                int top = ( outyr0 + 1 ) / 2;
                int bottom = Mini( ( inyr1 + 1 ) / 2, limit1 );
                int y;
                c2_t p = c2Add( ci, c2Scale( e1, top ) );
                for ( y = top * 2; y <= bottom * 2; y += 2, p = c2Add( p, e1 ) ) {
                    if ( ! READ_PIXEL( p ) ) {
                        break;
                    }
                    // pixels that force ray corrections are lit too
                    WRITE_PIXEL( p, 255 );
                }
                bounds0 = c2xy( i2 - 1, y - 1 );
                inyr0 = ( i2 - 1 ) * bounds0.y / bounds0.x;
                outyr0 = ( i2 + 1 ) * bounds0.y / bounds0.x;
            }
            c2_t lastin = c2Add( ci, c2Scale( e1, ( inyr1 + 1 ) / 2 ) );
            c2_t lastout = c2Add( ci, c2Scale( e1, ( outyr1 + 1 ) / 2 ) );
            if ( ( IS_ON_MAP( lastin ) && ! READ_PIXEL( lastin ) )
                && ( IS_ON_MAP( lastout ) && ! READ_PIXEL( lastout ) ) ) {
                bounds1 = r1;
            } else {
                int top = ( outyr0 + 1 ) / 2;
                int bottom = Mini( ( inyr1 + 1 ) / 2, limit1 );
                int y;
                c2_t p = c2Add( ci, c2Scale( e1, bottom ) );
                for ( y = bottom * 2; y >= top * 2; y -= 2, p = c2Sub( p, e1 ) ) {
                    if ( ! READ_PIXEL( p ) ) {
                        break;
                    }
                    // pixels that force ray corrections are lit too
                    WRITE_PIXEL( p, 255 );
                }
                bounds1 = c2xy( i2 + 1, y + 1 );
                inyr1 = ( i2 - 1 ) * bounds1.y / bounds1.x;
                outyr1 = ( i2 + 1 ) * bounds1.y / bounds1.x;
            }

            // closed frustum - quit
            if ( c2CrossC( bounds0, bounds1 ) <= 0 ) {
                continue;
            }

            // push actual rays
            {
                ADD_RAY( bounds0 );
                int top = ( outyr0 + 1 ) / 2;
                int bottom = Mini( ( inyr1 + 1 ) / 2, limit1 );
                c2_t p = c2Add( ci, c2Scale( e1, top ) );
                int prevPixel = READ_PIXEL( p );
                for ( int y = top * 2; y <= bottom * 2; y += 2, p = c2Add( p, e1 ) ) {
                    int pixel = READ_PIXEL( p );
                    if ( prevPixel != pixel ) {
                        c2_t ray;
                        if ( pixel ) {
                            ray = c2xy( i2 + 1, y - 1 );
                        } else {
                            ray = c2xy( i2 - 1, y - 1 );
                        }
                        ADD_RAY( ray );
                    }
                    prevPixel = pixel;
                }
                ADD_RAY( bounds1 );
            }
        }
        ci = c2Add( ci, e0 );
    }

    if ( ! skipAttenuation ) {
        c2_t ci = origin;
        int rsq = radius * radius;
        for ( int i = 0; i <= limit0; i++ ) {
            c2_t p = ci;
            for ( int j = 0; j <= limit1; j++ ) {
                c2_t d = c2Sub( p, origin );
                int dsq = c2Dot( d, d );
                int mod = 255 - Mini( dsq * 255 / rsq, 255 );
                int lit = !! outBitmap[p.x + p.y * bitmapWidth];
                WRITE_PIXEL( p, mod * lit );
                p = c2Add( p, e1 );
            }
            ci = c2Add( ci, e0 );
        }
    } else if ( ! skipClampToRadius ) {
        c2_t ci = origin;
        int rsq = radius * radius;
        for ( int i = 0; i <= limit0; i++ ) {
            c2_t p = ci;
            for ( int j = 0; j <= limit1; j++ ) {
                c2_t d = c2Sub( p, origin );
                if ( c2Dot( d, d ) > rsq ) { 
                    WRITE_PIXEL( p, 0 );
                }
                p = c2Add( p, e1 );
            }
            ci = c2Add( ci, e0 );
        }
    }

    if ( darkWalls ) {
        c2_t ci = origin;
        for ( int i = 0; i <= limit0; i++ ) {
            c2_t p = ci;
            for ( int j = 0; j <= limit1; j++ ) {
                if ( READ_PIXEL( p ) ) { 
                    WRITE_PIXEL( p, 0 );
                }
                p = c2Add( p, e1 );
            }
            ci = c2Add( ci, e0 );
        }
    } 
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.