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.03';
9              
10 3     3   249642 use strict;
  3         25  
  3         127  
11 3     3   17 use warnings;
  3         5  
  3         78  
12 3     3   1197 use Math::Trig ':pi';
  3         30600  
  3         463  
13              
14             require XSLoader;
15              
16 3     3   27 use base qw(Exporter);
  3         9  
  3         4595  
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 17922 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   425 bypairall( sub { $callback->( $x + $_[0], $y + $_[1] ) },
128 3         12 @{ $circle_points{$radius} } );
  3         15  
129             }
130              
131             sub raycast {
132 3     3 1 7521 my ( $circle_cb, $line_cb, $x, $y, @rest ) = @_;
133 3     40   31 $circle_cb->( sub { line( $line_cb, $x, $y, $_[0], $_[1] ) }, $x, $y, @rest );
  40         252  
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 9697 my ( $startx, $starty, $radius, $bcb, $lcb, $rcb ) = @_;
140 2         7 $lcb->( $startx, $starty, 0, 0 );
141 2         32 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         33 _shadowcast( $startx, $starty, $radius, $bcb, $lcb, $rcb, 1, 1.0, 0.0, @$mult );
152             }
153             }
154              
155             sub _shadowcast {
156 16     16   38 my ( $startx, $starty, $radius, $bcb, $lcb, $rcb, $row, $light_start,
157             $light_end, $xx, $xy, $yx, $yy )
158             = @_;
159 16         21 my $blocked = 0;
160 16         20 my $new_start = 0.0;
161 16         31 for my $j ( $row .. $radius ) {
162 18         25 my $dy = -$j;
163 18         30 for my $dx ( $dy .. 0 ) {
164 38         83 my $rslope = ( $dx + 0.5 ) / ( $dy - 0.5 );
165 38         57 my $lslope = ( $dx - 0.5 ) / ( $dy + 0.5 );
166 38 100       111 if ( $light_start < $rslope ) { next }
  2 50       3  
167 0         0 elsif ( $light_end > $lslope ) { last }
168 36         56 my $curx = $startx + $dx * $xx + $dy * $xy;
169 36         45 my $cury = $starty + $dx * $yx + $dy * $yy;
170 36 100       70 $lcb->( $curx, $cury, $dx, $dy ) if $rcb->( $dx, $dy );
171 36 100       318 if ($blocked) {
172 10 100       21 if ( $bcb->( $curx, $cury, $dx, $dy ) ) {
173 8         35 $new_start = $rslope;
174 8         17 next;
175             } else {
176 2         10 $blocked = 0;
177 2         4 $light_start = $new_start;
178             }
179             } else {
180 26 100 66     63 if ( $bcb->( $curx, $cury, $dx, $dy ) and $j < $radius ) {
181 10         71 $blocked = 1;
182 10 50       21 _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       64 last if $blocked;
192             }
193             }
194              
195             sub swing_circle(&$$$$) {
196 3     3 1 7650 push @_, 0, pi2;
197 3         33 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__