Recursive Shadowcasting Algorithm

Godot Version

4.6

Question

Good afternoon! I’ve been trying to implement recursive shadowcasting for line of sight in my game and its been taking an embarrassingly long amount of time :sweat_smile:

func recursive_shadowcasting(start : Vector2i):
	for i in range(rows):
		for j in range(columns):
			if Vector2i(i,j) != start:board[i][j].blocked = true
	for i in range(8):
		recursive(i, -1,1,1, start,ret)

func transform(octant:int, c:Vector2):
	match octant:
		0: return Vector2(-c.x,  c.y)
		1: return Vector2(c.y,  -c.x)
		2: return Vector2( c.y,  c.x)
		3: return Vector2( c.x,  c.y)
		4: return Vector2( c.x, -c.y)
		5: return Vector2(-c.y, c.x)
		6: return Vector2(-c.y, -c.x)
		7: return Vector2(-c.x, -c.y)
##you must assume that a spot has a center at (.5,.5) and that (0,0) represents
##the topleft corner
func recursive(octant : int, start_slope : float,end_slope : float,depth : int,start_position : Vector2i, array = []):
	var start = start_position+get_octant(octant)*depth 
	var end  = start_position+get_octant((octant+1)%8)*depth
	var dir = (end-start).sign()
	var curr = start
	if !get_tile(start) and !get_tile(end):
		return
	var first = true
	var curr_as_tile : Spot
	var prev_as_tile : Spot
	
	while curr != end:
		curr_as_tile = get_tile(curr)
		##standardize so that curr-start_position is always the positive distance
		##between the two
		var local = transform(octant, curr-start_position)
		##the top left
		var l_slope = float(local.y)/ float(local.x)
		##the bottom right
		var r_slope = float(local.y+1)/ float(local.x+1)
		if curr_as_tile:
			if l_slope >= start_slope and r_slope <= end_slope:
				if !curr_as_tile.blocker():
					curr_as_tile.blocked = false
				if !first:
					if !prev_as_tile.blocker() and curr_as_tile.blocker():
						recursive(octant,start_slope,l_slope, depth+1, start_position)
					elif prev_as_tile.blocker() and !curr_as_tile.blocker():
						var l = transform(octant, get_coordinates_by_tile(prev_as_tile)-start_position)
						
						##new start is the previous ones bottom right
						start_slope = float(l.y+1)/ float(l.x+1)
				else:
					first = false
			else:
				curr_as_tile.disabled=true
			prev_as_tile = curr_as_tile
		curr+=dir
	##if the last one at this depth is a blocker, 
	##that means we sent a new recursive call.
	##let that handle further action instead,
	##since it has the updated end slope
	if (prev_as_tile and !prev_as_tile.blocker()):
		recursive(octant,start_slope,end_slope, depth+1, start_position,array)

the issues I’m facing almost certainly have to do with how I’m calculating slopes, or the transform() being wrong (however on a blank field it seems to hit all of the spots in each octant just fine..). Here are the results for the 2 main cases of the algorithm:

I know this is a pretty niche topic, but any help would be greatly appreciated! Thank you.

It’s not really clear from your images what the problem is. Put some more obstacles in and run only the first octant. Post the results of the whole sweep, preferably in top down view.

1 Like

Sure.

in this example, i’d expect the last 2 squares in the creature’s row to be visible. I’d also expect the spots along the diagonal line to be visible.

however, with 2 consecutive blocks at the same depth, I do get the intended behavior.

here’s an illustrated example in another octant. red lines represent the initial start and end slopes, while the blue line represents the new starting slope after hitting the case where a previous spot was a blocker while the current one is transparent.

Hope these help!

Looks like you just haven’t implemented the algorithm properly.

Can you do the same task by rotating a triangle and testing whether the tile point is inside ? Theres an easy recipe on here:

Edit: scrap that, you just need a bunch of rays that you rotate, theyre in the direction your cone casts in, then just check each tile along the ray until you hit an obstacle … so you need a function that says whether the ray is in the tile, and that could be a 2D line rasterizer or a ray cast with line to quad intersection testing.

That wouldn’t really calculate the visibility from a specific position.

[quote=“normalized, post:6, topic:137666, full:true”]

That wouldn’t really calculate the visibility from a specific position.

[/quote]

Yeah sorry i was thinking of something similar to my own problem of 2D frustrum culling. I edited the reply.