File Coverage

blib/lib/Game/RaycastFOV.pm
Criterion Covered Total %
statement 57 58 98.2
branch 17 18 94.4
condition 2 3 66.6
subroutine 11 11 100.0
pod 4 4 100.0
total 91 94 96.8


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