1 #include "shadowcasting.h"
2 
3 #include <cstdint>
4 #include <cstdlib>
5 #include <iterator>
6 
7 #include "cuboid_rectangle.h"
8 #include "fragment_cloud.h" // IWYU pragma: keep
9 #include "line.h"
10 #include "list.h"
11 #include "point.h"
12 
13 struct slope {
slopeslope14     slope( int_least8_t rise, int_least8_t run ) {
15         // Ensure run is always positive for the inequality operators
16         this->run = std::abs( run );
17         if( run < 0 ) {
18             this->rise = -rise;
19         } else {
20             this->rise = rise;
21         }
22     }
23 
24     // We don't need more that 8 bits since the shadowcasting area is not that large,
25     // currently the radius is 60.
26     int_least8_t rise;
27     int_least8_t run;
28 };
29 
operator <(const slope & lhs,const slope & rhs)30 static bool operator<( const slope &lhs, const slope &rhs )
31 {
32     // a/b < c/d <=> a*d < c*b if b and d have the same sign.
33     return lhs.rise * rhs.run < rhs.rise * lhs.run;
34 }
operator >(const slope & lhs,const slope & rhs)35 static bool operator>( const slope &lhs, const slope &rhs )
36 {
37     return rhs < lhs;
38 }
operator ==(const slope & lhs,const slope & rhs)39 static bool operator==( const slope &lhs, const slope &rhs )
40 {
41     // a/b == c/d <=> a*d == c*b
42     return lhs.rise * rhs.run == rhs.rise * lhs.run;
43 }
44 
45 template<typename T>
46 struct span {
spanspan47     span( const slope &s_major, const slope &e_major,
48           const slope &s_minor, const slope &e_minor,
49           const T &value, bool skip_first_row = false ) :
50         start_major( s_major ), end_major( e_major ), start_minor( s_minor ), end_minor( e_minor ),
51         cumulative_value( value ), skip_first_row( skip_first_row ) {}
52     slope start_major;
53     slope end_major;
54     slope start_minor;
55     slope end_minor;
56     T cumulative_value;
57     bool skip_first_row;
58 };
59 
60 /**
61  * Handle splitting the current span in cast_horizontal_zlight_segment and
62  * cast_vertical_zlight_segment to avoid as much code duplication as possible
63  */
64 template<typename T, bool( *is_transparent )( const T &, const T & ), T( *accumulate )( const T &, const T &, const int & )>
split_span(cata::list<span<T>> & spans,typename cata::list<span<T>>::iterator & this_span,T & current_transparency,const T & new_transparency,const T & last_intensity,const int distance,slope & new_start_minor,const slope & trailing_edge_major,const slope & leading_edge_major,const slope & trailing_edge_minor,const slope & leading_edge_minor)65 static void split_span( cata::list<span<T>> &spans,
66                         typename cata::list<span<T>>::iterator &this_span,
67                         T &current_transparency, const T &new_transparency, const T &last_intensity,
68                         const int distance, slope &new_start_minor,
69                         const slope &trailing_edge_major, const slope &leading_edge_major,
70                         const slope &trailing_edge_minor, const slope &leading_edge_minor )
71 {
72     const T next_cumulative_transparency = accumulate( this_span->cumulative_value,
73                                            current_transparency, distance );
74     // We split the span into up to 4 sub-blocks (sub-frustums actually,
75     // this is the view from the origin looking out):
76     // +-------+ <- end major
77     // |   D   |
78     // +---+---+ <- leading edge major
79     // | B | C |
80     // +---+---+ <- trailing edge major
81     // |   A   |
82     // +-------+ <- start major
83     // ^       ^
84     // |       end minor
85     // start minor
86     // A is previously processed row(s). This might be empty.
87     // B is already-processed tiles from current row. This must exist.
88     // C is remainder of current row. This must exist.
89     // D is not yet processed row(s). Might be empty.
90     // A, B and D have the previous transparency, C has the new transparency,
91     //   which might be opaque.
92     // One we processed fully in 2D and only need to extend in last D
93     // Only emit a new span horizontally if previous span was not opaque.
94     // If end_minor is <= trailing_edge_minor, A, B, C remain one span and
95     //  D becomes a new span if present.
96     // If this is the first row processed in the current span, there is no A span.
97     // If this is the last row processed, there is no D span.
98     // If check returns false, A and B are opaque and have no spans.
99     if( is_transparent( current_transparency, last_intensity ) ) {
100         // Emit the A span if present, placing it before the current span in the list
101         if( trailing_edge_major > this_span->start_major ) {
102             spans.emplace( this_span,
103                            this_span->start_major, trailing_edge_major,
104                            this_span->start_minor, this_span->end_minor,
105                            next_cumulative_transparency );
106         }
107 
108         // Emit the B span if present, placing it before the current span in the list
109         if( trailing_edge_minor > this_span->start_minor ) {
110             spans.emplace( this_span,
111                            std::max( this_span->start_major, trailing_edge_major ),
112                            std::min( this_span->end_major, leading_edge_major ),
113                            this_span->start_minor, trailing_edge_minor,
114                            next_cumulative_transparency );
115         }
116 
117         // Overwrite new_start_minor since previous tile is transparent.
118         new_start_minor = trailing_edge_minor;
119     }
120 
121     // Emit the D span if present, placing it after the current span in the list
122     if( leading_edge_major < this_span->end_major ) {
123         // Pass true to the span constructor to set skip_first_row to true
124         // This prevents the same row we are currently checking being checked by the
125         // new D span
126         spans.emplace( std::next( this_span ),
127                        leading_edge_major, this_span->end_major,
128                        this_span->start_minor, this_span->end_minor,
129                        this_span->cumulative_value, true );
130     }
131 
132     // Truncate this_span to the current block.
133     this_span->start_major = std::max( this_span->start_major, trailing_edge_major );
134     this_span->end_major = std::min( this_span->end_major, leading_edge_major );
135 
136     // The new span starts at the leading edge of the previous square if it is opaque,
137     // and at the trailing edge of the current square if it is transparent.
138     this_span->start_minor = new_start_minor;
139 
140     new_start_minor = leading_edge_minor;
141     current_transparency = new_transparency;
142 }
143 
144 template<int xx_transform, int xy_transform, int yx_transform, int yy_transform, int z_transform, typename T,
145          T( *calc )( const T &, const T &, const int & ),
146          bool( *is_transparent )( const T &, const T & ),
147          T( *accumulate )( const T &, const T &, const int & )>
cast_horizontal_zlight_segment(const array_of_grids_of<T> & output_caches,const array_of_grids_of<const T> & input_arrays,const array_of_grids_of<const bool> & floor_caches,const tripoint & offset,const int offset_distance,const T numerator)148 void cast_horizontal_zlight_segment(
149     const array_of_grids_of<T> &output_caches,
150     const array_of_grids_of<const T> &input_arrays,
151     const array_of_grids_of<const bool> &floor_caches,
152     const tripoint &offset, const int offset_distance,
153     const T numerator )
154 {
155     const int radius = 60 - offset_distance;
156 
157     constexpr int min_z = -OVERMAP_DEPTH;
158     constexpr int max_z = OVERMAP_HEIGHT;
159     static half_open_rectangle<point> bounds( point_zero, point( MAPSIZE_X, MAPSIZE_Y ) );
160 
161     slope new_start_minor( 1, 1 );
162 
163     T last_intensity( 0.0 );
164     tripoint delta;
165     tripoint current;
166 
167     // We start out with one span covering the entire horizontal and vertical space
168     // we are interested in.  Then as changes in transparency are encountered, we truncate
169     // that initial span and insert new spans before/after it in the list, removing any that
170     // are no longer needed as we go.
171     cata::list<span<T>> spans = { {
172             slope( 0, 1 ), slope( 1, 1 ),
173             slope( 0, 1 ), slope( 1, 1 ),
174             T( LIGHT_TRANSPARENCY_OPEN_AIR )
175         }
176     };
177     // At each "depth", a.k.a. distance from the origin, we iterate once over the list of spans,
178     // possibly splitting them.
179     for( int distance = 1; distance <= radius; distance++ ) {
180         delta.y = distance;
181         T current_transparency( 0.0f );
182 
183         for( auto this_span = spans.begin(); this_span != spans.end(); ) {
184             bool started_block = false;
185             // TODO: Precalculate min/max delta.z based on start/end and distance
186             for( delta.z = 0; delta.z <= distance; delta.z++ ) {
187                 // Shadowcasting sweeps from the cardinal to the most extreme edge of the octant
188                 // XXXX
189                 // --->
190                 // XXX
191                 // -->
192                 // XX
193                 // ->
194                 // X
195                 // @
196                 //
197                 // Trailing edge -> +-
198                 //                  |+| <- Center of tile
199                 //                   -+ <- Leading Edge
200                 //  Direction of sweep --->
201                 // Use corners of given tile as above to determine angles of
202                 // leading and trailing edges being considered.
203                 const slope trailing_edge_major( delta.z * 2 - 1, delta.y * 2 + 1 );
204                 const slope leading_edge_major( delta.z * 2 + 1, delta.y * 2 - 1 );
205                 current.z = offset.z + delta.z * z_transform;
206                 if( current.z > max_z || current.z < min_z ) {
207                     // Current tile is out of bounds, advance to the next tile.
208                     continue;
209                 } else if( this_span->start_major > leading_edge_major ) {
210                     // Current span has a higher z-value,
211                     // jump to next iteration to catch up.
212                     continue;
213                 } else if( this_span->skip_first_row && this_span->start_major == leading_edge_major ) {
214                     // Prevents an infinite loop in some cases after splitting off the D span.
215                     // We don't want to recheck the row that just caused the D span to be split off,
216                     // since that can lead to an identical span being split off again, hence the
217                     // infinite loop.
218                     //
219                     // This could also be accomplished by adding a small epsilon to the start_major
220                     // of the D span but that causes artifacts.
221                     continue;
222                 } else if( this_span->end_major < trailing_edge_major ) {
223                     // We've escaped the bounds of the current span we're considering,
224                     // So continue to the next span.
225                     break;
226                 }
227 
228                 bool started_span = false;
229                 const int z_index = current.z + OVERMAP_DEPTH;
230                 for( delta.x = 0; delta.x <= distance; delta.x++ ) {
231                     current.x = offset.x + delta.x * xx_transform + delta.y * xy_transform;
232                     current.y = offset.y + delta.x * yx_transform + delta.y * yy_transform;
233                     // See definition of trailing_edge_major and leading_edge_major for clarification.
234                     const slope trailing_edge_minor( delta.x * 2 - 1, delta.y * 2 + 1 );
235                     const slope leading_edge_minor( delta.x * 2 + 1, delta.y * 2 - 1 );
236 
237                     if( !bounds.contains( current.xy() ) ) {
238                         // Current tile is out of bounds, advance to the next tile.
239                         continue;
240                     } else if( this_span->start_minor > leading_edge_minor ) {
241                         // Current tile comes before the span we're considering, advance to the next tile.
242                         continue;
243                     } else if( this_span->end_minor < trailing_edge_minor ) {
244                         // Current tile is after the span we're considering, continue to next row.
245                         break;
246                     }
247 
248                     T new_transparency = ( *input_arrays[z_index] )[current.x][current.y];
249 
250                     // If we're looking at a tile with floor or roof from the floor/roof side,
251                     // that tile is actually invisible to us.
252                     // TODO: Revisit this logic and differentiate between "can see bottom of tile"
253                     // and "can see majority of tile".
254                     bool floor_block = false;
255                     if( current.z < offset.z ) {
256                         if( ( *floor_caches[z_index + 1] )[current.x][current.y] ) {
257                             floor_block = true;
258                             new_transparency = LIGHT_TRANSPARENCY_SOLID;
259                         }
260                     } else if( current.z > offset.z ) {
261                         if( ( *floor_caches[z_index] )[current.x][current.y] ) {
262                             floor_block = true;
263                             new_transparency = LIGHT_TRANSPARENCY_SOLID;
264                         }
265                     }
266 
267                     if( !started_block ) {
268                         started_block = true;
269                         current_transparency = new_transparency;
270                     }
271 
272                     const int dist = rl_dist( tripoint_zero, delta ) + offset_distance;
273                     last_intensity = calc( numerator, this_span->cumulative_value, dist );
274 
275                     if( !floor_block ) {
276                         ( *output_caches[z_index] )[current.x][current.y] =
277                             std::max( ( *output_caches[z_index] )[current.x][current.y], last_intensity );
278                     }
279 
280                     if( !started_span ) {
281                         // Need to reset minor slope, because we're starting a new line
282                         new_start_minor = leading_edge_minor;
283                         started_span = true;
284                     }
285 
286                     if( new_transparency == current_transparency ) {
287                         // All in order, no need to split the span.
288                         new_start_minor = leading_edge_minor;
289                         continue;
290                     }
291 
292                     // Handle splitting the span into up to 4 separate spans
293                     split_span<T, is_transparent, accumulate>( spans, this_span, current_transparency,
294                             new_transparency, last_intensity,
295                             distance, new_start_minor,
296                             trailing_edge_major, leading_edge_major,
297                             trailing_edge_minor, leading_edge_minor );
298                 }
299 
300                 // If we end the row with an opaque tile, set the span to start at the next row
301                 // since we don't need to process the current one any more.
302                 if( !is_transparent( current_transparency, last_intensity ) ) {
303                     this_span->start_major = leading_edge_major;
304                 }
305             }
306 
307             if( !started_block ) {
308                 // If we didn't scan at least 1 z-level, don't iterate further
309                 // Otherwise we may "phase" through tiles without checking them or waste time
310                 // checking spans that are out of bounds.
311                 this_span = spans.erase( this_span );
312             } else if( !is_transparent( current_transparency, last_intensity ) ) {
313                 // If we reach the end of the span with terrain being opaque, we don't iterate further.
314                 // This means that any encountered transparent tiles from the current span have been
315                 // split off into new spans
316                 this_span = spans.erase( this_span );
317             } else {
318                 // Cumulative average of the values encountered.
319                 this_span->cumulative_value = accumulate( this_span->cumulative_value,
320                                               current_transparency, distance );
321                 ++this_span;
322             }
323         }
324     }
325 }
326 
327 template<int x_transform, int y_transform, int z_transform, typename T,
328          T( *calc )( const T &, const T &, const int & ),
329          bool( *is_transparent )( const T &, const T & ),
330          T( *accumulate )( const T &, const T &, const int & )>
cast_vertical_zlight_segment(const array_of_grids_of<T> & output_caches,const array_of_grids_of<const T> & input_arrays,const array_of_grids_of<const bool> & floor_caches,const tripoint & offset,const int offset_distance,const T numerator)331 void cast_vertical_zlight_segment(
332     const array_of_grids_of<T> &output_caches,
333     const array_of_grids_of<const T> &input_arrays,
334     const array_of_grids_of<const bool> &floor_caches,
335     const tripoint &offset, const int offset_distance,
336     const T numerator )
337 {
338     const int radius = 60 - offset_distance;
339 
340     constexpr int min_z = -OVERMAP_DEPTH;
341     constexpr int max_z = OVERMAP_HEIGHT;
342 
343     slope new_start_minor( 1, 1 );
344 
345     T last_intensity( 0.0 );
346     tripoint delta;
347     tripoint current;
348 
349     // We start out with one span covering the entire horizontal and vertical space
350     // we are interested in.  Then as changes in transparency are encountered, we truncate
351     // that initial span and insert new spans before/after it in the list, removing any that
352     // are no longer needed as we go.
353     cata::list<span<T>> spans = { {
354             slope( 0, 1 ), slope( 1, 1 ),
355             slope( 0, 1 ), slope( 1, 1 ),
356             T( LIGHT_TRANSPARENCY_OPEN_AIR )
357         }
358     };
359     // At each "depth", a.k.a. distance from the origin, we iterate once over the list of spans,
360     // possibly splitting them.
361     for( int distance = 1; distance <= radius; distance++ ) {
362         delta.z = distance;
363         T current_transparency( 0.0f );
364 
365         for( auto this_span = spans.begin(); this_span != spans.end(); ) {
366             bool started_block = false;
367             for( delta.y = 0; delta.y <= distance; delta.y++ ) {
368                 // See comment above trailing_edge_major and leading_edge_major in above function.
369                 const slope trailing_edge_major( delta.y * 2 - 1, delta.z * 2 + 1 );
370                 const slope leading_edge_major( delta.y * 2 + 1, delta.z * 2 - 1 );
371                 current.y = offset.y + delta.y * y_transform;
372                 if( current.y < 0 || current.y >= MAPSIZE_Y ) {
373                     // Current tile is out of bounds, advance to the next tile.
374                     continue;
375                 } else if( this_span->start_major > leading_edge_major ) {
376                     // Current span has a higher z-value,
377                     // jump to next iteration to catch up.
378                     continue;
379                 } else if( this_span->skip_first_row && this_span->start_major == leading_edge_major ) {
380                     // Prevents an infinite loop in some cases after splitting off the D span.
381                     // We don't want to recheck the row that just caused the D span to be split off,
382                     // since that can lead to an identical span being split off again, hence the
383                     // infinite loop.
384                     //
385                     // This could also be accomplished by adding a small epsilon to the start_major
386                     // of the D span but that causes artifacts.
387                     continue;
388                 } else if( this_span->end_major < trailing_edge_major ) {
389                     // We've escaped the bounds of the current span we're considering,
390                     // So continue to the next span.
391                     break;
392                 }
393 
394                 bool started_span = false;
395                 for( delta.x = 0; delta.x <= distance; delta.x++ ) {
396                     current.x = offset.x + delta.x * x_transform;
397                     current.z = offset.z + delta.z * z_transform;
398                     // See comment above trailing_edge_major and leading_edge_major in above function.
399                     const slope trailing_edge_minor( delta.x * 2 - 1, delta.z * 2 + 1 );
400                     const slope leading_edge_minor( delta.x * 2 + 1, delta.z * 2 - 1 );
401 
402                     if( current.x < 0 || current.x >= MAPSIZE_X ||
403                         current.z > max_z || current.z < min_z ) {
404                         // Current tile is out of bounds, advance to the next tile.
405                         continue;
406                     } else if( this_span->start_minor > leading_edge_minor ) {
407                         // Current tile comes before the span we're considering, advance to the next tile.
408                         continue;
409                     } else if( this_span->end_minor < trailing_edge_minor ) {
410                         // Current tile is after the span we're considering, continue to next row.
411                         break;
412                     }
413 
414                     const int z_index = current.z + OVERMAP_DEPTH;
415 
416                     T new_transparency = ( *input_arrays[z_index] )[current.x][current.y];
417 
418                     // If we're looking at a tile with floor or roof from the floor/roof side,
419                     // that tile is actually invisible to us.
420                     bool floor_block = false;
421                     if( current.z < offset.z ) {
422                         if( ( *floor_caches[z_index + 1] )[current.x][current.y] ) {
423                             floor_block = true;
424                             new_transparency = LIGHT_TRANSPARENCY_SOLID;
425                         }
426                     } else if( current.z > offset.z ) {
427                         if( ( *floor_caches[z_index] )[current.x][current.y] ) {
428                             floor_block = true;
429                             new_transparency = LIGHT_TRANSPARENCY_SOLID;
430                         }
431                     }
432 
433                     if( !started_block ) {
434                         started_block = true;
435                         current_transparency = new_transparency;
436                     }
437 
438                     const int dist = rl_dist( tripoint_zero, delta ) + offset_distance;
439                     last_intensity = calc( numerator, this_span->cumulative_value, dist );
440 
441                     if( !floor_block ) {
442                         ( *output_caches[z_index] )[current.x][current.y] =
443                             std::max( ( *output_caches[z_index] )[current.x][current.y], last_intensity );
444                     }
445 
446                     if( !started_span ) {
447                         // Need to reset minor slope, because we're starting a new line
448                         new_start_minor = leading_edge_minor;
449                         started_span = true;
450                     }
451 
452                     if( new_transparency == current_transparency ) {
453                         // All in order, no need to split the span.
454                         new_start_minor = leading_edge_minor;
455                         continue;
456                     }
457 
458                     // Handle splitting the span into up to 4 separate spans
459                     split_span<T, is_transparent, accumulate>( spans, this_span, current_transparency,
460                             new_transparency, last_intensity,
461                             distance, new_start_minor,
462                             trailing_edge_major, leading_edge_major,
463                             trailing_edge_minor, leading_edge_minor );
464                 }
465 
466                 // If we end the row with an opaque tile, set the span to start at the next row
467                 // since we don't need to process the current one any more.
468                 if( !is_transparent( current_transparency, last_intensity ) ) {
469                     this_span->start_major = leading_edge_major;
470                 }
471             }
472 
473             if( !started_block ) {
474                 // If we didn't scan at least 1 z-level, don't iterate further
475                 // Otherwise we may "phase" through tiles without checking them or waste time
476                 // checking spans that are out of bounds.
477                 this_span = spans.erase( this_span );
478             } else if( !is_transparent( current_transparency, last_intensity ) ) {
479                 // If we reach the end of the span with terrain being opaque, we don't iterate further.
480                 // This means that any encountered transparent tiles from the current span have been
481                 // split off into new spans
482                 this_span = spans.erase( this_span );
483             } else {
484                 // Cumulative average of the values encountered.
485                 this_span->cumulative_value = accumulate( this_span->cumulative_value,
486                                               current_transparency, distance );
487                 ++this_span;
488             }
489         }
490     }
491 }
492 
493 template<typename T, T( *calc )( const T &, const T &, const int & ),
494          bool( *is_transparent )( const T &, const T & ),
495          T( *accumulate )( const T &, const T &, const int & )>
cast_zlight(const array_of_grids_of<T> & output_caches,const array_of_grids_of<const T> & input_arrays,const array_of_grids_of<const bool> & floor_caches,const tripoint & origin,const int offset_distance,const T numerator,vertical_direction dir)496 void cast_zlight(
497     const array_of_grids_of<T> &output_caches,
498     const array_of_grids_of<const T> &input_arrays,
499     const array_of_grids_of<const bool> &floor_caches,
500     const tripoint &origin, const int offset_distance, const T numerator, vertical_direction dir )
501 {
502     if( dir == vertical_direction::DOWN || dir == vertical_direction::BOTH ) {
503         // Down lateral
504         // @..
505         //  ..
506         //   .
507         cast_horizontal_zlight_segment < 0, 1, 1, 0, -1, T, calc, is_transparent, accumulate > (
508             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
509         // @
510         // ..
511         // ...
512         cast_horizontal_zlight_segment < 1, 0, 0, 1, -1, T, calc, is_transparent, accumulate > (
513             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
514         //   .
515         //  ..
516         // @..
517         cast_horizontal_zlight_segment < 0, -1, 1, 0, -1, T, calc, is_transparent, accumulate > (
518             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
519         // ...
520         // ..
521         // @
522         cast_horizontal_zlight_segment < -1, 0, 0, 1, -1, T, calc, is_transparent, accumulate > (
523             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
524         // ..@
525         // ..
526         // .
527         cast_horizontal_zlight_segment < 0, 1, -1, 0, -1, T, calc, is_transparent, accumulate > (
528             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
529         //   @
530         //  ..
531         // ...
532         cast_horizontal_zlight_segment < 1, 0, 0, -1, -1, T, calc, is_transparent, accumulate > (
533             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
534         // .
535         // ..
536         // ..@
537         cast_horizontal_zlight_segment < 0, -1, -1, 0, -1, T, calc, is_transparent, accumulate > (
538             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
539         // ...
540         //  ..
541         //   @
542         cast_horizontal_zlight_segment < -1, 0, 0, -1, -1, T, calc, is_transparent, accumulate > (
543             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
544 
545         // Straight down
546         // @.
547         // ..
548         cast_vertical_zlight_segment < 1, 1, -1, T, calc, is_transparent, accumulate > (
549             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
550         // ..
551         // @.
552         cast_vertical_zlight_segment < 1, -1, -1, T, calc, is_transparent, accumulate > (
553             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
554         // .@
555         // ..
556         cast_vertical_zlight_segment < -1, 1, -1, T, calc, is_transparent, accumulate > (
557             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
558         // ..
559         // .@
560         cast_vertical_zlight_segment < -1, -1, -1, T, calc, is_transparent, accumulate > (
561             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
562     }
563 
564     if( dir == vertical_direction::UP || dir == vertical_direction::BOTH ) {
565         // Up lateral
566         // @..
567         //  ..
568         //   .
569         cast_horizontal_zlight_segment < 0, 1, 1, 0, 1, T, calc, is_transparent, accumulate > (
570             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
571         // @
572         // ..
573         // ...
574         cast_horizontal_zlight_segment < 1, 0, 0, 1, 1, T, calc, is_transparent, accumulate > (
575             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
576         // ..@
577         // ..
578         // .
579         cast_horizontal_zlight_segment < 0, -1, 1, 0, 1, T, calc, is_transparent, accumulate > (
580             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
581         //   @
582         //  ..
583         // ...
584         cast_horizontal_zlight_segment < -1, 0, 0, 1, 1, T, calc, is_transparent, accumulate > (
585             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
586         //   .
587         //  ..
588         // @..
589         cast_horizontal_zlight_segment < 0, 1, -1, 0, 1, T, calc, is_transparent, accumulate > (
590             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
591         // ...
592         // ..
593         // @
594         cast_horizontal_zlight_segment < 1, 0, 0, -1, 1, T, calc, is_transparent, accumulate > (
595             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
596         // .
597         // ..
598         // ..@
599         cast_horizontal_zlight_segment < 0, -1, -1, 0, 1, T, calc, is_transparent, accumulate > (
600             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
601         // ...
602         //  ..
603         //   @
604         cast_horizontal_zlight_segment < -1, 0, 0, -1, 1, T, calc, is_transparent, accumulate > (
605             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
606 
607         // Straight up
608         // @.
609         // ..
610         cast_vertical_zlight_segment < 1, 1, 1, T, calc, is_transparent, accumulate > (
611             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
612         // ..
613         // @.
614         cast_vertical_zlight_segment < 1, -1, 1, T, calc, is_transparent, accumulate > (
615             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
616         // .@
617         // ..
618         cast_vertical_zlight_segment < -1, 1, 1, T, calc, is_transparent, accumulate > (
619             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
620         // ..
621         // .@
622         cast_vertical_zlight_segment < -1, -1, 1, T, calc, is_transparent, accumulate > (
623             output_caches, input_arrays, floor_caches, origin, offset_distance, numerator );
624     }
625 }
626 
627 // I can't figure out how to make implicit instantiation work when the parameters of
628 // the template-supplied function pointers are involved, so I'm explicitly instantiating instead.
629 template void cast_zlight<float, sight_calc, sight_check, accumulate_transparency>(
630     const std::array<float ( * )[MAPSIZE_X][MAPSIZE_Y], OVERMAP_LAYERS> &output_caches,
631     const std::array<const float ( * )[MAPSIZE_X][MAPSIZE_Y], OVERMAP_LAYERS> &input_arrays,
632     const std::array<const bool ( * )[MAPSIZE_X][MAPSIZE_Y], OVERMAP_LAYERS> &floor_caches,
633     const tripoint &origin, int offset_distance, float numerator,
634     vertical_direction dir );
635 
636 template void cast_zlight<fragment_cloud, shrapnel_calc, shrapnel_check, accumulate_fragment_cloud>(
637     const std::array<fragment_cloud( * )[MAPSIZE_X][MAPSIZE_Y], OVERMAP_LAYERS> &output_caches,
638     const std::array<const fragment_cloud( * )[MAPSIZE_X][MAPSIZE_Y], OVERMAP_LAYERS>
639     &input_arrays,
640     const std::array<const bool ( * )[MAPSIZE_X][MAPSIZE_Y], OVERMAP_LAYERS> &floor_caches,
641     const tripoint &origin, int offset_distance, fragment_cloud numerator,
642     vertical_direction dir );
643