Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

shadowcasting algorithm part 1

position-based angles

the core idea:
implement a simple data structure containing two points, left and right, representing vectors in 2D space from the origin point (0,0). they are treated as rays that point out an infinite distance. we also need a function pointWithin(p) that returns true if p is between left and right

the advantage of this? you can apply shadowcasting to any coordinate system that can map two points => a straight line in 2D space. i think

image.png

image.png

you can determine if p is within this range using only multiplication, which avoids the kind of floating point numbers you get from sin, cos

if you have 3 points (a,b,p), you can derive the signed area of a triangle using the determinant of a 3x3 matrix, but if (ax,ay)=(0,0) this simplifies to:

bx * py - px * by

if the area of triangle(a,b,p) is

  • positive: p lies to the left of b. (a,b,p) is counter-clockwise
  • negative: p lies to the right of b. (a,b,p) is clockwise
  • zero: (a,b,p) are collinear

let q = test(left, right). if q is

  • negative: the range is less than 180 degrees
  • positive: the range is greater than 180 degrees
  • zero: left,right are collinear, and the range is 180 degrees if dot(left,right) < 0 and empty otherwise

special cases:
if the range is zero degrees we mark the data structure empty=true
if the range is 360 degrees (eg a full rotation) we mark the data structure engulfed=true. it felt like an appropriate name for a shadowcaster, this will come up later when we calculate the union of two ranges

we then define a function pointWithin(p) which returns true if p is between left and right, using results from test(left, p), test(right, p)

interactive desmos graph

code:

function Sector(_left, _right) constructor {
	left = arrayAppend( , _left);
	right = arrayAppend( , _right);
	
	static test = function(a, b) {
		return a[0]*b[1] - b[0]*a[1];
	};
	
	q = -1;
	empty = false; // no collisions
	engulfed = false; // full circle engulfed
	
	static update = function() {
		if (engulfed) return;
		
		// q < 0 := angle < 180
		// q > 0 := angle > 180
		q = test(left, right);
		
		// q == 0 := angle = 180 or angle = 0
		var collinear = (abs(q) <= math_get_epsilon());
		
		q = collinear ? 0 : sign(q);
		
		// ldotr > 0 := angle < 90 or angle > 270
		ldotr = dot2(left, right);
		
		if (collinear && ldotr > 0) {
			empty = true;
		}
	};
	update();
	
	static pointWithin = function(p) {
		if (engulfed) return true;
		if (empty) return false;
		
		var sleft = test(left, p);
		var sright = test(right, p);
		
		if (q < 0) {
			return (sleft <= 0 && sright >= 0);
		}
		
		if (q > 0) {
			return (sleft <= 0 || sright >= 0);
		}
		
		// if (q == 0):
		return (sleft <= 0);
	};
}

part 2

Support this post

Did you like this post? Tell us

Leave a comment

Log in with your itch.io account to leave a comment.