Isaac s fast beamcasting LOS
Jump to navigation
Jump to search
See also Isaac Kuo's java applet demo of this algorithm.
Isaac's fast beamcasting LOS - Isaac Kuo [mechdan@yahoo.com] Here I present pseudocode for a fast LOS calculator. This pseudocode calculates visibility in the first quadrant by casting beams at 32 different slopes. Despite the low number of casting angles, the fact that wide beams are thrown instead of thin lines ensures adequate coverage. Conceptually, the beam's source is the diagonal from (-.5,.5) to (.5,-.5). Each step of throwing the beam increments its position from the current diagonal line to the next. It is sufficient to check against collisions with squares along the diagonals because that is where their greatest extents will always be present. This pseudocode uses a skewed coordinate system to assist the calculations. The transformed coordinate system is: x=u-v/32 u=x+y y=v/32 v=y*32 ...initialize trans[x][y] to be the transparency map... ...initialize visible[x][y] to false... // Build a circular border of opaque blocks so later // there won\'t be any need for extra code to check // against maximum range. // // Note that there are obvious optimizations to make // this step MUCH faster. for(x=0;x<=MAXRANGE;x++) for(y=0;y<=MAXRANGE;y++) if (distance(x,y)>=MAXRANGE) trans[x][y]=false; // Set 0,0 to be visible even if the player is // standing on something opaque visible[0][0]=true; // Check the orthogonal directions for(x=1; trans[x][0]; x++) visible[x][0]=true; for(y=1; trans[0][y]; y++) visible[0][y]=true; // Now loop on the diagonal directions for(slope=1; slope<=31; slope++) { // initialize the v coordinate and set the beam size // to maximum--mini and maxi store the beam's current // top and bottom positions. // As long as mini<maxi, the beam has some width. // When mini=maxi, the beam is a thin line. // When mini>maxi, the beam has been blocked. v=slope; mini=0; maxi=31; for(u=1; mini<=maxi; u++) { //loop on the u coordinate y=v>>5; x=u-y; //Do the transform cor=32 - (v&31); //calculate the position of block corner within beam if(mini<cor) { //beam is low enough to hit (x,y) block visible[x][y]=true; if(!trans[x][y]) mini=cor; //beam was partially blocked } if(maxi<cor) { //beam is high enough to hit (x-1,y+1) block visible[x-1][y+1]=true; if(!trans[x-1][y+1]) maxi=cor; //beam was partially blocked } v+=slope; //increment the beam's v coordinate } } The general picture of what's happening is: | | | | \32 | | \ | | \maxi | \| | ------\cor------+---- |\ | | \ | | \ | | \mini | | \ | Y | \0 | ^ | | | | | | |X,Y BLOCK| +-->X ------+---------+ The position of the beam is between the points designated "mini" and "maxi". The parts below "mini" and above "maxi" have already been obscured by shadow. If "cor" is between them, then this beam hits two blocks (the one at X,Y and the one at X-1,Y+1). Otherwise, only one of those blocks will be hit. This algorithm should be pretty fast as it is--certainly faster than the typical raycasting algorithm because this one throws essentially a bundle of rays at a time for roughly the same computational cost of two of the traditional rays. It's also more elegant than traditional raycasting. No additional hacks are required to fill in wall gaps or beautify corners. If an exact LOS path is required (i.e. for displaying ranged weapon animation and/or determining the area of effect of a linear attack spell), then a beam which hits the target can be narrowed down the ray at either (mini+cor)/2 or (maxi+cor)/2, depending on whether the target was in the (X,Y) or the (X-1,Y+1) block. This ray's path will look something like: @###### ***.... ..***.. ....**T This can lead to near diagonal shots intersecting almost twice as many blocks as you'd expect. As such, I would only consider the blocks where the ray segment covers at least 1/2 in either the X or Y dimension. This will reduce the ray's path to something like: @###### .**.... ...**.. .....*T -- _____ Isaac Kuo mechdan@yahoo.com __|_>o<_|__ /___________\ The most important way to defend one's nation \=\>-----</=/ is to make it worth defending. - a true patriot
Variant algorithm:
Just to let you know--I'm pondering a new approach to my beamcasting LOS concept which visits each block exactly once. My original algorithm visits blocks multiple times, which is efficient with my assumed data structures--a simple fast arrays for visibility and opaqueness. However, a lot of people like to use a more complex or object oriented data structure, where each access involves a lot of overhead. My basic idea is to cast all slopes in a quadrant at once, keeping a small fast array of mini[slope] and maxi[slope]. In the original algorithm, the slope is the outer loop, and the radius is the inner loop. In my new algorithm, the radius is the outer loop while the slope is the inner loop. Roughly: //initialize all mini and maxi mini[1...slopes-1] = 1 maxi[1...slopes-1] = slopes-1 for u = 1 to maxradius //loop on radius //initialize at slope=0 x = u+ox y = 0+oy cor = 0 opaqueA = opaque(x,y) opaqueB = opaque(x-1,y+1) for slope = 1 to slopes-1 //loop on slope cor += u //increment cor if cor>=slopes //check for overflow into next block cor -= slopes x-- y++ opaqueA = opaqueB opaqueB = opaque(x-1,y+1) //Now do the visibility stuff if maxi[slope]>cor visible[x,y] = true if opaqueA maxi[slope]=cor if mini[slope]<cor visible[x-1,y+1] = true if opaqueB mini[slope]=cor next slope next u Note that I've swapped the roles of "mini" and "maxi", and replaced "cor" with num_slopes-cor. The above is a rough idea, but it has some problems. For one thing, it always visits every block within the maximum radius, even if there are nearby blocking walls. This can be a major performance hit if much of the terrain is made up of twisting narrow corridors. Isaac Kuo