File Coverage

blib/lib/Game/RaycastFOV.pm
Criterion Covered Total %
statement 49 50 98.0
branch 14 16 87.5
condition 2 3 66.6
subroutine 11 11 100.0
pod 4 4 100.0
total 80 84 95.2


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # raycast and shadowcast field-of-view and related routines (see also
4             # the *.xs file)
5              
6             package Game::RaycastFOV;
7              
8             our $VERSION = '2.02';
9              
10 3     3   170973 use strict;
  3         17  
  3         70  
11 3     3   12 use warnings;
  3         5  
  3         64  
12 3     3   934 use Math::Trig ':pi';
  3         26456  
  3         330  
13              
14             require XSLoader;
15              
16 3     3   17 use base qw(Exporter);
  3         6  
  3         3644  
17             our @EXPORT_OK =
18             qw(bypair bypairall cached_circle circle line raycast shadowcast sub_circle swing_circle %circle_points);
19              
20             XSLoader::load( 'Game::RaycastFOV', $VERSION );
21              
22             # precomputed via swing_circle(). only up to 11 due to 80x24 terminal.
23             # can be added to or changed as desired by caller. one could for example
24             # have a 0 radius that only fills in the compass directions adjacent or
25             # other shapes suitable to the need at hand
26             #
27             # NOTE these may change to be more efficient at doing a minimally
28             # complete raycast instead of the complete exterior circle (which
29             # probably creates more raycasts than may be necessary)
30             our %circle_points = (
31             1 => [ 1, 0, 1, 1, 0, 1, -1, 1, -1, 0, -1, -1, 0, -1, 1, -1 ],
32             2 => [
33             2, 0, 2, 1, 1, 1, 1, 2, 0, 2, -1, 2, -1, 1, -2, 1,
34             -2, 0, -2, -1, -1, -1, -1, -2, 0, -2, 1, -2, 1, -1, 2, -1
35             ],
36             3 => [
37             3, 0, 3, 1, 2, 1, 2, 2, 1, 2, 1, 3, 0, 3, -1, 3,
38             -1, 2, -2, 2, -2, 1, -3, 1, -3, 0, -3, -1, -2, -1, -2, -2,
39             -1, -2, -1, -3, 0, -3, 1, -3, 1, -2, 2, -2, 2, -1, 3, -1
40             ],
41             4 => [
42             4, 0, 4, 1, 4, 2, 3, 2, 3, 3, 2, 3, 2, 4, 1, 4,
43             0, 4, -1, 4, -2, 4, -2, 3, -3, 3, -3, 2, -4, 2, -4, 1,
44             -4, 0, -4, -1, -4, -2, -3, -2, -3, -3, -2, -3, -2, -4, -1, -4,
45             0, -4, 1, -4, 2, -4, 2, -3, 3, -3, 3, -2, 4, -2, 4, -1
46             ],
47             5 => [
48             5, 0, 5, 1, 5, 2, 4, 2, 4, 3, 3, 3, 3, 4, 2, 4,
49             2, 5, 1, 5, 0, 5, -1, 5, -2, 5, -2, 4, -3, 4, -3, 3,
50             -4, 3, -4, 2, -5, 2, -5, 1, -5, 0, -5, -1, -5, -2, -4, -2,
51             -4, -3, -3, -3, -3, -4, -2, -4, -2, -5, -1, -5, 0, -5, 1, -5,
52             2, -5, 2, -4, 3, -4, 3, -3, 4, -3, 4, -2, 5, -2, 5, -1
53             ],
54             6 => [
55             6, 0, 6, 1, 6, 2, 5, 2, 5, 3, 5, 4, 4, 4, 4, 5,
56             3, 5, 2, 5, 2, 6, 1, 6, 0, 6, -1, 6, -2, 6, -2, 5,
57             -3, 5, -4, 5, -4, 4, -5, 4, -5, 3, -5, 2, -6, 2, -6, 1,
58             -6, 0, -6, -1, -6, -2, -5, -2, -5, -3, -5, -4, -4, -4, -4, -5,
59             -3, -5, -2, -5, -2, -6, -1, -6, 0, -6, 1, -6, 2, -6, 2, -5,
60             3, -5, 4, -5, 4, -4, 5, -4, 5, -3, 5, -2, 6, -2, 6, -1
61             ],
62             7 => [
63             7, 0, 7, 1, 7, 2, 6, 2, 6, 3, 6, 4, 5, 4, 5, 5,
64             4, 5, 4, 6, 3, 6, 2, 6, 2, 7, 1, 7, 0, 7, -1, 7,
65             -2, 7, -2, 6, -3, 6, -4, 6, -4, 5, -5, 5, -5, 4, -6, 4,
66             -6, 3, -6, 2, -7, 2, -7, 1, -7, 0, -7, -1, -7, -2, -6, -2,
67             -6, -3, -6, -4, -5, -4, -5, -5, -4, -5, -4, -6, -3, -6, -2, -6,
68             -2, -7, -1, -7, 0, -7, 1, -7, 2, -7, 2, -6, 3, -6, 4, -6,
69             4, -5, 5, -5, 5, -4, 6, -4, 6, -3, 6, -2, 7, -2, 7, -1
70             ],
71             8 => [
72             8, 0, 8, 1, 8, 2, 7, 2, 7, 3, 7, 4, 6, 4, 6, 5,
73             6, 6, 5, 6, 4, 6, 4, 7, 3, 7, 2, 7, 2, 8, 1, 8,
74             0, 8, -1, 8, -2, 8, -2, 7, -3, 7, -4, 7, -4, 6, -5, 6,
75             -6, 6, -6, 5, -6, 4, -7, 4, -7, 3, -7, 2, -8, 2, -8, 1,
76             -8, 0, -8, -1, -8, -2, -7, -2, -7, -3, -7, -4, -6, -4, -6, -5,
77             -6, -6, -5, -6, -4, -6, -4, -7, -3, -7, -2, -7, -2, -8, -1, -8,
78             0, -8, 1, -8, 2, -8, 2, -7, 3, -7, 4, -7, 4, -6, 5, -6,
79             6, -6, 6, -5, 6, -4, 7, -4, 7, -3, 7, -2, 8, -2, 8, -1
80             ],
81             9 => [
82             9, 0, 9, 1, 9, 2, 9, 3, 8, 3, 8, 4, 8, 5, 7, 5, 7, 6,
83             6, 6, 6, 7, 5, 7, 5, 8, 4, 8, 3, 8, 3, 9, 2, 9, 1, 9,
84             0, 9, -1, 9, -2, 9, -3, 9, -3, 8, -4, 8, -5, 8, -5, 7, -6, 7,
85             -6, 6, -7, 6, -7, 5, -8, 5, -8, 4, -8, 3, -9, 3, -9, 2, -9, 1,
86             -9, 0, -9, -1, -9, -2, -9, -3, -8, -3, -8, -4, -8, -5, -7, -5, -7, -6,
87             -6, -6, -6, -7, -5, -7, -5, -8, -4, -8, -3, -8, -3, -9, -2, -9, -1, -9,
88             0, -9, 1, -9, 2, -9, 3, -9, 3, -8, 4, -8, 5, -8, 5, -7, 6, -7,
89             6, -6, 7, -6, 7, -5, 8, -5, 8, -4, 8, -3, 9, -3, 9, -2, 9, -1
90             ],
91             10 => [
92             10, 0, 10, 1, 10, 2, 10, 3, 9, 3, 9, 4, 9, 5,
93             8, 5, 8, 6, 7, 6, 7, 7, 6, 7, 6, 8, 5, 8,
94             5, 9, 4, 9, 3, 9, 3, 10, 2, 10, 1, 10, 0, 10,
95             -1, 10, -2, 10, -3, 10, -3, 9, -4, 9, -5, 9, -5, 8,
96             -6, 8, -6, 7, -7, 7, -7, 6, -8, 6, -8, 5, -9, 5,
97             -9, 4, -9, 3, -10, 3, -10, 2, -10, 1, -10, 0, -10, -1,
98             -10, -2, -10, -3, -9, -3, -9, -4, -9, -5, -8, -5, -8, -6,
99             -7, -6, -7, -7, -6, -7, -6, -8, -5, -8, -5, -9, -4, -9,
100             -3, -9, -3, -10, -2, -10, -1, -10, 0, -10, 1, -10, 2, -10,
101             3, -10, 3, -9, 4, -9, 5, -9, 5, -8, 6, -8, 6, -7,
102             7, -7, 7, -6, 8, -6, 8, -5, 9, -5, 9, -4, 9, -3,
103             10, -3, 10, -2, 10, -1
104             ],
105             11 => [
106             11, 0, 11, 1, 11, 2, 11, 3, 10, 3, 10, 4, 10, 5,
107             9, 5, 9, 6, 9, 7, 8, 7, 8, 8, 7, 8, 7, 9,
108             6, 9, 5, 9, 5, 10, 4, 10, 3, 10, 3, 11, 2, 11,
109             1, 11, 0, 11, -1, 11, -2, 11, -3, 11, -3, 10, -4, 10,
110             -5, 10, -5, 9, -6, 9, -7, 9, -7, 8, -8, 8, -8, 7,
111             -9, 7, -9, 6, -9, 5, -10, 5, -10, 4, -10, 3, -11, 3,
112             -11, 2, -11, 1, -11, 0, -11, -1, -11, -2, -11, -3, -10, -3,
113             -10, -4, -10, -5, -9, -5, -9, -6, -9, -7, -8, -7, -8, -8,
114             -7, -8, -7, -9, -6, -9, -5, -9, -5, -10, -4, -10, -3, -10,
115             -3, -11, -2, -11, -1, -11, 0, -11, 1, -11, 2, -11, 3, -11,
116             3, -10, 4, -10, 5, -10, 5, -9, 6, -9, 7, -9, 7, -8,
117             8, -8, 8, -7, 9, -7, 9, -6, 9, -5, 10, -5, 10, -4,
118             10, -3, 11, -3, 11, -2, 11, -1
119             ]
120             );
121              
122             # the lack of checks are for speed, use at your own risk
123             sub cached_circle (&$$$) {
124 3     3 1 9882 my ( $callback, $x, $y, $radius ) = @_;
125             # process all the points on the assumption that the callback will
126             # abort say line drawing should that wander outside a level map
127 40     40   327 bypairall( sub { $callback->( $x + $_[0], $y + $_[1] ) },
128 3         9 @{ $circle_points{$radius} } );
  3         14  
129             }
130              
131             sub raycast {
132 3     3 1 4490 my ( $circle_cb, $line_cb, $x, $y, @rest ) = @_;
133 3     40   18 $circle_cb->( sub { line( $line_cb, $x, $y, $_[0], $_[1] ) }, $x, $y, @rest );
  40         186  
134             }
135              
136             # http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting
137             # or in particular the Java and Ruby implementations
138             sub shadowcast {
139 2     2 1 2175 my ( $startx, $starty, $radius, $bcb, $lcb, $rcb ) = @_;
140 2         7 $lcb->( $startx, $starty, 0, 0 );
141 2         28 for my $mult (
142             [ 1, 0, 0, 1 ],
143             [ 0, 1, 1, 0 ],
144             [ 0, -1, 1, 0 ],
145             [ -1, 0, 0, 1 ],
146             [ -1, 0, 0, -1 ],
147             [ 0, -1, -1, 0 ],
148             [ 0, 1, -1, 0 ],
149             [ 1, 0, 0, -1 ]
150             ) {
151 16         28 _shadowcast( $startx, $starty, $radius, $bcb, $lcb, $rcb, 1, 1.0, 0.0, @$mult );
152             }
153             }
154              
155             sub _shadowcast {
156 16     16   25 my ( $startx, $starty, $radius, $bcb, $lcb, $rcb, $row, $light_start,
157             $light_end, $xx, $xy, $yx, $yy )
158             = @_;
159 16         18 my $blocked = 0;
160 16         18 my $new_start = 0.0;
161 16         24 for my $j ( $row .. $radius ) {
162 18         22 my $dy = -$j;
163 18         27 for my $dx ( $dy .. 0 ) {
164 38         67 my $rslope = ( $dx + 0.5 ) / ( $dy - 0.5 );
165 38         43 my $lslope = ( $dx - 0.5 ) / ( $dy + 0.5 );
166 38 100       72 if ( $light_start < $rslope ) { next }
  2 50       3  
167 0         0 elsif ( $light_end > $lslope ) { last }
168 36         40 my $curx = $startx + $dx * $xx + $dy * $xy;
169 36         42 my $cury = $starty + $dx * $yx + $dy * $yy;
170 36 100       50 $lcb->( $curx, $cury, $dx, $dy ) if $rcb->( $dx, $dy );
171 36 100       268 if ($blocked) {
172 10 100       14 if ( $bcb->( $curx, $cury, $dx, $dy ) ) {
173 8         32 $new_start = $rslope;
174 8         12 next;
175             } else {
176 2         7 $blocked = 0;
177 2         5 $light_start = $new_start;
178             }
179             } else {
180 26 100 66     38 if ( $bcb->( $curx, $cury, $dx, $dy ) and $j < $radius ) {
181 10         72 $blocked = 1;
182 10 50       16 _shadowcast(
183             $startx, $starty, $radius, $bcb, $lcb,
184             $rcb, $j + 1, $light_start, $lslope, $xx,
185             $xy, $yx, $yy
186             ) unless $light_start < $lslope;
187 10         18 $new_start = $rslope;
188             }
189             }
190             }
191 18 100       53 last if $blocked;
192             }
193             }
194              
195             sub swing_circle(&$$$$) {
196 3     3 1 4449 push @_, 0, pi2;
197 3         24 goto &sub_circle;
198             }
199              
200             # for reference; converted to XS in version 2.02 with the following
201             # matching and updated code not being quite so stupid about rounding
202             # ints and thus not needing a plus 0.5 fudge factor
203             #sub swing_circle (&$$$$) {
204             # my ( $callback, $x, $y, $radius, $swing ) = @_;
205             # my $angle = 0;
206             # my %seen;
207             # while ( $angle < pi2 ) {
208             # my $nx = $x + sprintf( "%.0f", $radius * cos $angle );
209             # my $ny = $y + sprintf( "%.0f", $radius * sin $angle );
210             # $callback->( $nx, $ny ) unless $seen{ $nx . ',' . $ny }++;
211             # $angle += $swing;
212             # }
213             #}
214              
215             1;
216             __END__