diff --git a/src/lightmap.cpp b/src/lightmap.cpp index 7eb12309cd0b2..1bd5fade03ca3 100644 --- a/src/lightmap.cpp +++ b/src/lightmap.cpp @@ -721,283 +721,6 @@ static constexpr quadrant quadrant_from_x_y( int x, int y ) ( ( y > 0 ) ? quadrant::NE : quadrant::SE ); } -// Add defaults for when method is invoked for the first time. -template -void cast_zlight_segment( - const std::array &output_caches, - const std::array &input_arrays, - const std::array &floor_caches, - const tripoint &offset, const int offset_distance, - const T numerator = 1.0f, const int row = 1, - float start_major = 0.0f, const float end_major = 1.0f, - float start_minor = 0.0f, const float end_minor = 1.0f, - T cumulative_transparency = LIGHT_TRANSPARENCY_OPEN_AIR ); - -template -void cast_zlight_segment( - const std::array &output_caches, - const std::array &input_arrays, - const std::array &floor_caches, - const tripoint &offset, const int offset_distance, - const T numerator, const int row, - float start_major, const float end_major, - float start_minor, const float end_minor, - T cumulative_transparency ) -{ - if( start_major >= end_major || start_minor >= end_minor ) { - return; - } - - float radius = 60.0f - offset_distance; - - constexpr int min_z = -OVERMAP_DEPTH; - constexpr int max_z = OVERMAP_HEIGHT; - - float new_start_minor = 1.0f; - - T last_intensity = 0.0; - static constexpr tripoint origin( 0, 0, 0 ); - tripoint delta( 0, 0, 0 ); - tripoint current( 0, 0, 0 ); - for( int distance = row; distance <= radius; distance++ ) { - delta.y = distance; - bool started_block = false; - T current_transparency = 0.0f; - - // TODO: Precalculate min/max delta.z based on start/end and distance - for( delta.z = 0; delta.z <= distance; delta.z++ ) { - float trailing_edge_major = ( delta.z - 0.5f ) / ( delta.y + 0.5f ); - float leading_edge_major = ( delta.z + 0.5f ) / ( delta.y - 0.5f ); - current.z = offset.z + delta.x * 00 + delta.y * 00 + delta.z * zz; - if( current.z > max_z || current.z < min_z ) { - continue; - } else if( start_major > leading_edge_major ) { - continue; - } else if( end_major < trailing_edge_major ) { - break; - } - - bool started_span = false; - const int z_index = current.z + OVERMAP_DEPTH; - for( delta.x = 0; delta.x <= distance; delta.x++ ) { - current.x = offset.x + delta.x * xx + delta.y * xy + delta.z * xz; - current.y = offset.y + delta.x * yx + delta.y * yy + delta.z * yz; - float trailing_edge_minor = ( delta.x - 0.5f ) / ( delta.y + 0.5f ); - float leading_edge_minor = ( delta.x + 0.5f ) / ( delta.y - 0.5f ); - - if( !( current.x >= 0 && current.y >= 0 && - current.x < MAPSIZE_X && - current.y < MAPSIZE_Y ) || start_minor > leading_edge_minor ) { - continue; - } else if( end_minor < trailing_edge_minor ) { - break; - } - - T new_transparency = ( *input_arrays[z_index] )[current.x][current.y]; - // If we're looking at a tile with floor or roof from the floor/roof side, - // that tile is actually invisible to us. - bool floor_block = false; - if( current.z < offset.z ) { - if( z_index < ( OVERMAP_LAYERS - 1 ) && - ( *floor_caches[z_index + 1] )[current.x][current.y] ) { - floor_block = true; - new_transparency = LIGHT_TRANSPARENCY_SOLID; - } - } else if( current.z > offset.z ) { - if( ( *floor_caches[z_index] )[current.x][current.y] ) { - floor_block = true; - new_transparency = LIGHT_TRANSPARENCY_SOLID; - } - } - - if( !started_block ) { - started_block = true; - current_transparency = new_transparency; - } - - const int dist = rl_dist( origin, delta ) + offset_distance; - last_intensity = calc( numerator, cumulative_transparency, dist ); - - if( !floor_block ) { - ( *output_caches[z_index] )[current.x][current.y] = - std::max( ( *output_caches[z_index] )[current.x][current.y], last_intensity ); - } - - if( !started_span ) { - // Need to reset minor slope, because we're starting a new line - new_start_minor = leading_edge_minor; - // Need more precision or artifacts happen - leading_edge_minor = start_minor; - started_span = true; - } - - if( new_transparency == current_transparency ) { - // All in order, no need to recurse - new_start_minor = leading_edge_minor; - continue; - } - - // We split the block into 4 sub-blocks (sub-frustums actually, this is the view from the origin looking out): - // +-------+ <- end major - // | D | - // +---+---+ <- ??? - // | B | C | - // +---+---+ <- major mid - // | A | - // +-------+ <- start major - // ^ ^ - // | end minor - // start minor - // A is previously processed row(s). - // B is already-processed tiles from current row. - // C is remainder of current row. - // D is not yet processed row(s). - // One we processed fully in 2D and only need to extend in last D - // Only cast recursively horizontally if previous span was not opaque. - if( check( current_transparency, last_intensity ) ) { - T next_cumulative_transparency = accumulate( cumulative_transparency, current_transparency, - distance ); - // Blocks can be merged if they are actually a single rectangle - // rather than rectangle + line shorter than rectangle's width - const bool merge_blocks = end_minor <= trailing_edge_minor; - // trailing_edge_major can be less than start_major - const float trailing_clipped = std::max( trailing_edge_major, start_major ); - const float major_mid = merge_blocks ? leading_edge_major : trailing_clipped; - cast_zlight_segment( - output_caches, input_arrays, floor_caches, - offset, offset_distance, numerator, distance + 1, - start_major, major_mid, start_minor, end_minor, - next_cumulative_transparency ); - if( !merge_blocks ) { - // One line that is too short to be part of the rectangle above - cast_zlight_segment( - output_caches, input_arrays, floor_caches, - offset, offset_distance, numerator, distance + 1, - major_mid, leading_edge_major, start_minor, trailing_edge_minor, - next_cumulative_transparency ); - } - } - - // One from which we shaved one line ("processed in 1D") - const float old_start_minor = start_minor; - // The new span starts at the leading edge of the previous square if it is opaque, - // and at the trailing edge of the current square if it is transparent. - if( !check( current_transparency, last_intensity ) ) { - start_minor = new_start_minor; - } else { - // Note this is the same slope as one of the recursive calls we just made. - start_minor = std::max( start_minor, trailing_edge_minor ); - start_major = std::max( start_major, trailing_edge_major ); - } - - // leading_edge_major plus some epsilon - float after_leading_edge_major = ( delta.z + 0.50001f ) / ( delta.y - 0.5f ); - cast_zlight_segment( - output_caches, input_arrays, floor_caches, - offset, offset_distance, numerator, distance, - after_leading_edge_major, end_major, old_start_minor, start_minor, - cumulative_transparency ); - - // One we just entered ("processed in 0D" - the first point) - // No need to recurse, we're processing it right now - - current_transparency = new_transparency; - new_start_minor = leading_edge_minor; - } - - if( !check( current_transparency, last_intensity ) ) { - start_major = leading_edge_major; - } - } - - if( !started_block ) { - // If we didn't scan at least 1 z-level, don't iterate further - // Otherwise we may "phase" through tiles without checking them - break; - } - - if( !check( current_transparency, last_intensity ) ) { - // If we reach the end of the span with terrain being opaque, we don't iterate further. - break; - } - // Cumulative average of the values encountered. - cumulative_transparency = accumulate( cumulative_transparency, current_transparency, distance ); - } -} - -template -void cast_zlight( - const std::array &output_caches, - const std::array &input_arrays, - const std::array &floor_caches, - const tripoint &origin, const int offset_distance, const T numerator ) -{ - // Down - cast_zlight_segment < 0, 1, 0, 1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment < 1, 0, 0, 0, 1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - - cast_zlight_segment < 0, -1, 0, 1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment < -1, 0, 0, 0, 1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - - cast_zlight_segment < 0, 1, 0, -1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment < 1, 0, 0, 0, -1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - - cast_zlight_segment < 0, -1, 0, -1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment < -1, 0, 0, 0, -1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - - // Up - cast_zlight_segment<0, 1, 0, 1, 0, 0, 1, T, calc, check, accumulate>( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment<1, 0, 0, 0, 1, 0, 1, T, calc, check, accumulate>( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - - cast_zlight_segment < 0, -1, 0, 1, 0, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment < -1, 0, 0, 0, 1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - - cast_zlight_segment < 0, 1, 0, -1, 0, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment < 1, 0, 0, 0, -1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - - cast_zlight_segment < 0, -1, 0, -1, 0, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - cast_zlight_segment < -1, 0, 0, 0, -1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); -} - -// I can't figure out how to make implicit instantiation work when the parameters of -// the template-supplied function pointers are involved, so I'm explicitly instantiating instead. -template void cast_zlight( - const std::array &output_caches, - const std::array &input_arrays, - const std::array &floor_caches, - const tripoint &origin, const int offset_distance, const float numerator ); - -template void cast_zlight( - const std::array &output_caches, - const std::array - &input_arrays, - const std::array &floor_caches, - const tripoint &origin, const int offset_distance, const fragment_cloud numerator ); - template + +#include "enums.h" +#include "fragment_cloud.h" // IWYU pragma: keep +#include "line.h" + +struct slope { + slope( int_least8_t rise, int_least8_t run ) { + // Ensure run is always positive for the inequality operators + this->run = abs( run ); + if( run < 0 ) { + this->rise = -rise; + } else { + this->rise = rise; + } + } + + // We don't need more that 8 bits since the shadowcasting area is not that large, + // currently the radius is 60. + int_least8_t rise; + int_least8_t run; +}; + +inline bool operator<( const slope &lhs, const slope &rhs ) +{ + // a/b < c/d <=> a*d < c*b if b and d have the same sign. + return lhs.rise * rhs.run < rhs.rise * lhs.run; +} +inline bool operator>( const slope &lhs, const slope &rhs ) +{ + return rhs < lhs; +} +inline bool operator<=( const slope &lhs, const slope &rhs ) +{ + return !( lhs > rhs ); +} +inline bool operator>=( const slope &lhs, const slope &rhs ) +{ + return !( lhs < rhs ); +} +inline bool operator==( const slope &lhs, const slope &rhs ) +{ + // a/b == c/d <=> a*d == c*b + return lhs.rise * rhs.run == rhs.rise * lhs.run; +} +inline bool operator!=( const slope &lhs, const slope &rhs ) +{ + return !( lhs == rhs ); +} + +template +struct span { + span( const slope &s_major, const slope &e_major, + const slope &s_minor, const slope &e_minor, + const T &value, bool skip_first_row = false ) : + start_major( s_major ), end_major( e_major ), start_minor( s_minor ), end_minor( e_minor ), + cumulative_value( value ), skip_first_row( skip_first_row ) {} + slope start_major; + slope end_major; + slope start_minor; + slope end_minor; + T cumulative_value; + bool skip_first_row; +}; + +/** + * Handle splitting the current span in cast_horizontal_zlight_segment and + * cast_vertical_zlight_segment to avoid as much code duplication as possible + */ +/*template +static void split_span( std::list> &spans, typename std::list>::iterator &this_span, + T ¤t_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 ) +{ + +} +*/ +template +void cast_horizontal_zlight_segment( + const std::array &output_caches, + const std::array &input_arrays, + const std::array &floor_caches, + const tripoint &offset, const int offset_distance, + const T numerator ) +{ + const int radius = 60 - offset_distance; + + constexpr int min_z = -OVERMAP_DEPTH; + constexpr int max_z = OVERMAP_HEIGHT; + + T last_intensity = 0.0; + static constexpr tripoint origin( 0, 0, 0 ); + tripoint delta( 0, 0, 0 ); + tripoint current( 0, 0, 0 ); + + // We start out with one span covering the entire horizontal and vertical space + // we are interested in. Then as changes in transparency are encountered, we truncate + // that initial span and insert new spans before/after it in the list, removing any that + // are no longer needed as we go. + std::list> spans = { { + slope( 0, 1 ), slope( 1, 1 ), + slope( 0, 1 ), slope( 1, 1 ), + LIGHT_TRANSPARENCY_OPEN_AIR + } + }; + // At each "depth", a.k.a. distance from the origin, we iterate once over the list of spans, + // possibly splitting them. + for( int distance = 1; distance <= radius && !spans.empty(); distance++ ) { + delta.y = distance; + T current_transparency = 0.0f; + + for( auto this_span = spans.begin(); this_span != spans.end(); ) { + bool started_block = false; + bool first_row = true; + std::bitset<128> previous_row_transparencies; + std::bitset<128> current_row_transparencies; + + slope current_start_minor = slope( 1, 1 ); + + // TODO: Precalculate min/max delta.z based on start/end and distance + for( delta.z = 0; delta.z <= distance; delta.z++ ) { + const slope trailing_edge_major( delta.z * 2 - 1, delta.y * 2 + 1 ); + const slope leading_edge_major( delta.z * 2 + 1, delta.y * 2 - 1 ); + current.z = offset.z + delta.z * z_transform; + if( current.z > max_z || current.z < min_z ) { + // Current tile is out of bounds, advance to the next tile. + continue; + } else if( this_span->start_major > leading_edge_major ) { + // Current span has a higher z-value, + // jump to next iteration to catch up. + continue; + } else if( this_span->skip_first_row && this_span->start_major == leading_edge_major ) { + // Prevents an infinite loop in some cases after splitting off the D span. + // We don't want to recheck the row that just caused the D span to be split off, + // since that can lead to an identical span being split off again, hence the + // infinite loop. + // + // This could also be accomplished by adding a small epsilon to the start_major + // of the D span but that causes artifacts. + continue; + } else if( this_span->end_major < trailing_edge_major ) { + // We've escaped the bounds of the current span we're considering, + // So continue to the next span. + break; + } + + const int z_index = current.z + OVERMAP_DEPTH; + + current_start_minor = this_span->start_minor; + + for( delta.x = 0; delta.x <= distance; delta.x++ ) { + current.x = offset.x + delta.x * xx_transform + delta.y * xy_transform; + current.y = offset.y + delta.x * yx_transform + delta.y * yy_transform; + // Shadowcasting sweeps from the cardinal to the most extreme edge of the octant + // XXXX + // ---> + // XXX + // --> + // XX + // -> + // X + // @ + // + // Trailing edge -> +- + // |+| <- Center of tile + // -+ <- Leading Edge + // Direction of sweep ---> + // Use corners of given tile as above to determine angles of + // leading and trailing edges being considered. + const slope trailing_edge_minor( delta.x * 2 - 1, delta.y * 2 + 1 ); + const slope leading_edge_minor( delta.x * 2 + 1, delta.y * 2 - 1 ); + + if( current.x < 0 || current.x >= MAPSIZE_X || + current.y < 0 || current.y >= MAPSIZE_Y ) { + // Current tile is out of bounds, advance to the next tile. + continue; + } else if( this_span->start_minor > leading_edge_minor ) { + // Current tile comes before the span we're considering, advance to the next tile. + continue; + } else if( this_span->end_minor < trailing_edge_minor ) { + // Current tile is after the span we're considering, continue to next row. + break; + } + + T new_transparency = ( *input_arrays[z_index] )[current.x][current.y]; + + // If we're looking at a tile with floor or roof from the floor/roof side, + // that tile is actually invisible to us. + bool floor_block = false; + if( current.z < offset.z ) { + if( z_index < ( OVERMAP_LAYERS - 1 ) && + ( *floor_caches[z_index + 1] )[current.x][current.y] ) { + floor_block = true; + new_transparency = LIGHT_TRANSPARENCY_SOLID; + } + } else if( current.z > offset.z ) { + if( ( *floor_caches[z_index] )[current.x][current.y] ) { + floor_block = true; + new_transparency = LIGHT_TRANSPARENCY_SOLID; + } + } + + if( !started_block ) { + started_block = true; + current_transparency = new_transparency; + } + + + const int dist = rl_dist( origin, delta ) + offset_distance; + last_intensity = calc( numerator, this_span->cumulative_value, dist ); + + current_row_transparencies[delta.x] = is_transparent( new_transparency, last_intensity ); + + if( !floor_block ) { + ( *output_caches[z_index] )[current.x][current.y] = + std::max( ( *output_caches[z_index] )[current.x][current.y], last_intensity ); + } + + if( new_transparency == current_transparency && ( first_row || delta.x == 0 || + previous_row_transparencies[delta.x - 1] == previous_row_transparencies[delta.x] ) ) { + // All in order, no need to split the span. + continue; + } + + // Handle spliting the span into up to 4 separate spans + // split_span( spans, this_span, current_transparency, + // new_transparency, last_intensity, + // distance, new_start_minor, + // trailing_edge_major, leading_edge_major, + // trailing_edge_minor, leading_edge_minor ); + + const T next_cumulative_transparency = accumulate( this_span->cumulative_value, + current_transparency, distance ); + // We split off any spans we encounter + // This is the view from the origin looking out: + // +-------+ <- end major + // | C | + // +---+ + <- leading edge major if B opaque, next trailing edge major + // | B | | if B transparent + // +---+---+ <- trailing edge major + // | A | + // +-------+ <- start major + // ^ ^ + // | end minor + // start minor + // A is previously processed row(s). This might be empty. + // B is already-processed tiles from current row. This might be empty. + // C is the remainder of current span. This must exist. + // A and B have the same, previous transparency. + // C has an indeterminate and possibly non-homogenous transparency. + + // If check returns false, A and B are opaque and have no spans. + if( is_transparent( current_transparency, last_intensity ) ) { + // Emit the A span if present, placing it before the current span in the list + if( trailing_edge_major > this_span->start_major ) { + spans.emplace( this_span, + this_span->start_major, trailing_edge_major, + this_span->start_minor, this_span->end_minor, + next_cumulative_transparency ); + + this_span->start_major = trailing_edge_major; + + // Since A is present we know that the previous row was entirely transparent + // Emit the B span if present, placing it before the current span in the list + if( trailing_edge_minor > current_start_minor ) { + // Previous row is transparent, so use the trailing edge of the current row as start major + // B is transparent, so use the trailing edge of the next row as end major + const slope next_trailing_edge_major( ( delta.z + 1 ) * 2 - 1, delta.y * 2 + 1 ); + spans.emplace( this_span, + this_span->start_major, next_trailing_edge_major, + this_span->start_minor, trailing_edge_minor, + next_cumulative_transparency ); + + current_start_minor = trailing_edge_minor; + } + } else if( trailing_edge_minor > current_start_minor ) { + // A span not present, so previous row might be opaque + // It is sufficient to check only the directly previous transparency since + // if there were any changes we would have detected them and already stopped + if( first_row || previous_row_transparencies[delta.x - 1] ) { + // Previous row is transparent, so use the trailing edge of the current row as start major + // B is transparent, so use the trailing edge of the next row as end major + const slope next_trailing_edge_major( ( delta.z + 1 ) * 2 - 1, delta.y * 2 + 1 ); + spans.emplace( this_span, + std::max( this_span->start_major, trailing_edge_major ), + next_trailing_edge_major, + this_span->start_minor, trailing_edge_minor, + next_cumulative_transparency ); + } else { + // Previous row is opaque, so use the leading edge of the previous row as start major + const slope previous_leading_edge_major( ( delta.z - 1 ) * 2 + 1, delta.y * 2 - 1 ); + // B is transparent, so use the trailing edge of the next row as end major + const slope next_trailing_edge_major( ( delta.z + 1 ) * 2 - 1, delta.y * 2 + 1 ); + spans.emplace( this_span, + std::max( this_span->start_major, previous_leading_edge_major ), + next_trailing_edge_major, + this_span->start_minor, trailing_edge_minor, + next_cumulative_transparency, true ); + } + + current_start_minor = trailing_edge_minor; + } + } else { + // Leading edge of the previous square since opaque, + const slope prev_leading_edge_major( ( delta.z - 1 ) * 2 + 1, delta.y * 2 - 1 ); + this_span->start_major = std::max( this_span->start_major, prev_leading_edge_major ); + + const slope prev_leading_edge_minor( ( delta.x - 1 ) * 2 + 1, delta.y * 2 - 1 ); + current_start_minor = std::max( current_start_minor, prev_leading_edge_minor ); + + } + + current_transparency = new_transparency; + } + + // If we end the row with an opaque tile, set the span to start at the next row + if( !is_transparent( current_transparency, last_intensity ) ) { + // We know the last tile was opaque, but there may have been transparent tiles + // earlier in the row. + const slope next_trailing_edge_major( ( delta.z + 1 ) * 2 - 1, delta.y * 2 + 1 ); + this_span->start_major = next_trailing_edge_major; + } else if( current_start_minor != this_span->start_minor ) { + // If a span was broken off earlier in this row, we need to break off the rest + // of the row to a new span and set the current span to start on the next row + + // Previous row is transparent, so use the trailing edge of the current row as start major + // this new span is transparent, so use the trailing edge of the next row as end major + const T next_cumulative_transparency = accumulate( this_span->cumulative_value, + current_transparency, distance ); + if( first_row || previous_row_transparencies[delta.x - 1] ) { + const slope next_trailing_edge_major( ( delta.z + 1 ) * 2 - 1, delta.y * 2 + 1 ); + spans.emplace( this_span, + std::max( this_span->start_major, trailing_edge_major ), + next_trailing_edge_major, + current_start_minor, this_span->end_minor, + next_cumulative_transparency ); + } else { + // Previous row is opaque, so use the leading edge of the previous row as start major + const slope previous_leading_edge_major( ( delta.z - 1 ) * 2 + 1, delta.y * 2 - 1 ); + // B is transparent, so use the trailing edge of the next row as end major + const slope next_trailing_edge_major( ( delta.z + 1 ) * 2 - 1, delta.y * 2 + 1 ); + spans.emplace( this_span, + std::max( this_span->start_major, previous_leading_edge_major ), + next_trailing_edge_major, + current_start_minor, this_span->end_minor, + next_cumulative_transparency, true ); + } + + const slope next_trailing_edge_major( ( delta.z + 1 ) * 2 - 1, delta.y * 2 + 1 ); + this_span->start_major = next_trailing_edge_major; + } + + previous_row_transparencies = std::move( current_row_transparencies ); + current_row_transparencies.reset(); + + if( started_block ) { + first_row = false; + } + } + + if( !started_block ) { + // If we didn't scan at least 1 z-level, don't iterate further + // Otherwise we may "phase" through tiles without checking them or waste time + // checking spans that are out of bounds. + this_span = spans.erase( this_span ); + } else if( !is_transparent( current_transparency, last_intensity ) ) { + // If we reach the end of the span with terrain being opaque, we don't iterate further. + // This means that any encountered transparent tiles from the current span have been + // split off into new spans + this_span = spans.erase( this_span ); + } else { + // Cumulative average of the values encountered. + this_span->cumulative_value = accumulate( this_span->cumulative_value, + current_transparency, distance ); + ++this_span; + } + } + } +} +/* +template +void cast_vertical_zlight_segment( + const std::array &output_caches, + const std::array &input_arrays, + const std::array &floor_caches, + const tripoint &offset, const int offset_distance, + const T numerator ) +{ + const int radius = 60 - offset_distance; + + constexpr int min_z = -OVERMAP_DEPTH; + constexpr int max_z = OVERMAP_HEIGHT; + + slope new_start_minor( 1, 1 ); + + T last_intensity = 0.0; + static constexpr tripoint origin( 0, 0, 0 ); + tripoint delta( 0, 0, 0 ); + tripoint current( 0, 0, 0 ); + + // We start out with one span covering the entire horizontal and vertical space + // we are interested in. Then as changes in transparency are encountered, we truncate + // that initial span and insert new spans before/after it in the list, removing any that + // are no longer needed as we go. + std::list> spans = { { + slope( 0, 1 ), slope( 1, 1 ), + slope( 0, 1 ), slope( 1, 1 ), + LIGHT_TRANSPARENCY_OPEN_AIR + } + }; + // At each "depth", a.k.a. distance from the origin, we iterate once over the list of spans, + // possibly splitting them. + for( int distance = 1; distance <= radius; distance++ ) { + delta.z = distance; + T current_transparency = 0.0f; + + for( auto this_span = spans.begin(); this_span != spans.end(); ) { + bool started_block = false; + for( delta.y = 0; delta.y <= distance; delta.y++ ) { + const slope trailing_edge_major( delta.y * 2 - 1, delta.z * 2 + 1 ); + const slope leading_edge_major( delta.y * 2 + 1, delta.z * 2 - 1 ); + current.y = offset.y + delta.y * y_transform; + if( current.y < 0 || current.y >= MAPSIZE_Y ) { + // Current tile is out of bounds, advance to the next tile. + continue; + } else if( this_span->start_major > leading_edge_major ) { + // Current span has a higher z-value, + // jump to next iteration to catch up. + continue; + } else if( this_span->skip_first_row && this_span->start_major == leading_edge_major ) { + // Prevents an infinite loop in some cases after splitting off the D span. + // We don't want to recheck the row that just caused the D span to be split off, + // since that can lead to an identical span being split off again, hence the + // infinite loop. + // + // This could also be accomplished by adding a small epsilon to the start_major + // of the D span but that causes artifacts. + continue; + } else if( this_span->end_major < trailing_edge_major ) { + // We've escaped the bounds of the current span we're considering, + // So continue to the next span. + break; + } + + bool started_row = false; + for( delta.x = 0; delta.x <= distance; delta.x++ ) { + current.x = offset.x + delta.x * x_transform; + current.z = offset.z + delta.z * z_transform; + // Shadowcasting sweeps from the cardinal to the most extreme edge of the octant + // XXXX + // ---> + // XXX + // --> + // XX + // -> + // X + // @ + // + // Trailing edge -> +- + // |+| <- Center of tile + // -+ <- Leading Edge + // Direction of sweep ---> + // Use corners of given tile as above to determine angles of + // leading and trailing edges being considered. + const slope trailing_edge_minor( delta.x * 2 - 1, delta.z * 2 + 1 ); + const slope leading_edge_minor( delta.x * 2 + 1, delta.z * 2 - 1 ); + + if( current.x < 0 || current.x >= MAPSIZE_X || + current.z > max_z || current.z < min_z ) { + // Current tile is out of bounds, advance to the next tile. + continue; + } else if( this_span->start_minor > leading_edge_minor ) { + // Current tile comes before the span we're considering, advance to the next tile. + continue; + } else if( this_span->end_minor < trailing_edge_minor ) { + // Current tile is after the span we're considering, continue to next row. + break; + } + + const int z_index = current.z + OVERMAP_DEPTH; + + T new_transparency = ( *input_arrays[z_index] )[current.x][current.y]; + + // If we're looking at a tile with floor or roof from the floor/roof side, + // that tile is actually invisible to us. + bool floor_block = false; + if( current.z < offset.z ) { + if( z_index < ( OVERMAP_LAYERS - 1 ) && + ( *floor_caches[z_index + 1] )[current.x][current.y] ) { + floor_block = true; + new_transparency = LIGHT_TRANSPARENCY_SOLID; + } + } else if( current.z > offset.z ) { + if( ( *floor_caches[z_index] )[current.x][current.y] ) { + floor_block = true; + new_transparency = LIGHT_TRANSPARENCY_SOLID; + } + } + + if( !started_block ) { + started_block = true; + current_transparency = new_transparency; + } + + const int dist = rl_dist( origin, delta ) + offset_distance; + last_intensity = calc( numerator, this_span->cumulative_value, dist ); + + if( !floor_block ) { + ( *output_caches[z_index] )[current.x][current.y] = + std::max( ( *output_caches[z_index] )[current.x][current.y], last_intensity ); + } + + if( !started_row ) { + // Need to reset minor slope, because we're starting a new line + new_start_minor = leading_edge_minor; + started_row = true; + } + + if( new_transparency == current_transparency ) { + // All in order, no need to split the span. + new_start_minor = leading_edge_minor; + continue; + } + + // Handle spliting the span into up to 4 separate spans + split_span( spans, this_span, current_transparency, + new_transparency, last_intensity, + distance, new_start_minor, + trailing_edge_major, leading_edge_major, + trailing_edge_minor, leading_edge_minor ); + } + + // If we end the row with an opaque tile, set the span to start at the next row + // since we don't need to process the current one any more. + if( !is_transparent( current_transparency, last_intensity ) ) { + this_span->start_major = leading_edge_major; + } + } + + if( !started_block ) { + // If we didn't scan at least 1 z-level, don't iterate further + // Otherwise we may "phase" through tiles without checking them or waste time + // checking spans that are out of bounds. + this_span = spans.erase( this_span ); + } else if( !is_transparent( current_transparency, last_intensity ) ) { + // If we reach the end of the span with terrain being opaque, we don't iterate further. + // This means that any encountered transparent tiles from the current span have been + // split off into new spans + this_span = spans.erase( this_span ); + } else { + // Cumulative average of the values encountered. + this_span->cumulative_value = accumulate( this_span->cumulative_value, + current_transparency, distance ); + ++this_span; + } + } + } +} +*/ +template +void cast_zlight( + const std::array &output_caches, + const std::array &input_arrays, + const std::array &floor_caches, + const tripoint &origin, const int offset_distance, const T numerator ) +{ + // Down lateral + + // @.. + // .. + // . + cast_horizontal_zlight_segment < 0, 1, 1, 0, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // @ + // .. + // ... + cast_horizontal_zlight_segment < 1, 0, 0, 1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // ..@ + // .. + // . + cast_horizontal_zlight_segment < 0, -1, 1, 0, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + + // @ + // .. + // ... + cast_horizontal_zlight_segment < -1, 0, 0, 1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // . + // .. + // @.. + cast_horizontal_zlight_segment < 0, 1, -1, 0, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // ... + // .. + // @ + cast_horizontal_zlight_segment < 1, 0, 0, -1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // . + // .. + // ..@ + cast_horizontal_zlight_segment < 0, -1, -1, 0, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // ... + // .. + // @ + cast_horizontal_zlight_segment < -1, 0, 0, -1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + + // Up lateral + + // @.. + // .. + // . + cast_horizontal_zlight_segment < 0, 1, 1, 0, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // @ + // .. + // ... + cast_horizontal_zlight_segment < 1, 0, 0, 1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // ..@ + // .. + // . + cast_horizontal_zlight_segment < 0, -1, 1, 0, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // @ + // .. + // ... + cast_horizontal_zlight_segment < -1, 0, 0, 1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // . + // .. + // @.. + cast_horizontal_zlight_segment < 0, 1, -1, 0, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // ... + // .. + // @ + cast_horizontal_zlight_segment < 1, 0, 0, -1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // . + // .. + // ..@ + cast_horizontal_zlight_segment < 0, -1, -1, 0, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // ... + // .. + // @ + cast_horizontal_zlight_segment < -1, 0, 0, -1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + + // Straight up + /* + // @. + // .. + cast_vertical_zlight_segment < 1, 1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .. + // @. + cast_vertical_zlight_segment < 1, -1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .@ + // .. + cast_vertical_zlight_segment < -1, 1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .. + // .@ + cast_vertical_zlight_segment < -1, -1, 1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + + // Straight down + + // @. + // .. + cast_vertical_zlight_segment < 1, 1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .. + // @. + cast_vertical_zlight_segment < 1, -1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .@ + // .. + cast_vertical_zlight_segment < -1, 1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .. + // .@ + cast_vertical_zlight_segment < -1, -1, -1, T, calc, is_transparent, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + */ +} + +// I can't figure out how to make implicit instantiation work when the parameters of +// the template-supplied function pointers are involved, so I'm explicitly instantiating instead. +template void cast_zlight( + const std::array &output_caches, + const std::array &input_arrays, + const std::array &floor_caches, + const tripoint &origin, const int offset_distance, const float numerator ); + +template void cast_zlight( + const std::array &output_caches, + const std::array + &input_arrays, + const std::array &floor_caches, + const tripoint &origin, const int offset_distance, const fragment_cloud numerator ); diff --git a/tests/shadowcasting_test.cpp b/tests/shadowcasting_test.cpp index a6f10bfcc5756..c1075bb57bf3f 100644 --- a/tests/shadowcasting_test.cpp +++ b/tests/shadowcasting_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -412,197 +413,409 @@ static void shadowcasting_3d_2d( const int iterations ) #define V LIGHT_TRANSPARENCY_CLEAR #define X LIGHT_TRANSPARENCY_SOLID -const point ORIGIN( 65, 65 ); +const tripoint ORIGIN( 65, 65, 11 ); struct grid_overlay { - std::vector> data; - point offset; + std::vector>> data; + std::vector>> floor; + tripoint offset; float default_value; + bool default_floor = true; // origin_offset is specified as the coordinates of the "camera" within the overlay. grid_overlay( const point &origin_offset, const float default_value ) { this->offset = ORIGIN - origin_offset; this->default_value = default_value; } + grid_overlay( const tripoint origin_offset, const float default_value ) { + this->offset = ORIGIN - origin_offset; + this->default_value = default_value; + } - int height() const { + int depth() const { return data.size(); } - int width() const { + int height() const { if( data.empty() ) { return 0; } return data[0].size(); } + int width() const { + if( data.empty() || data[0].empty() ) { + return 0; + } + return data[0][0].size(); + } + tripoint get_max() const { + return offset + tripoint( width(), height(), depth() ); + } - float get_global( const int x, const int y ) const { + float get_global( const int x, const int y, const int z ) const { if( y >= offset.y && y < offset.y + height() && - x >= offset.x && x < offset.x + width() ) { - return data[ y - offset.y ][ x - offset.x ]; + x >= offset.x && x < offset.x + width() && + z >= offset.z && z < offset.z + depth() ) { + return data[ z - offset.z ][ y - offset.y ][ x - offset.x ]; } return default_value; } - - float get_local( const int x, const int y ) const { - return data[ y ][ x ]; + bool get_floor( const int x, const int y, const int z ) const { + if( !floor.empty() && + y >= offset.y && y < offset.y + height() && + x >= offset.x && x < offset.x + width() && + z >= offset.z && z < offset.z + depth() ) { + return data[ z - offset.z ][ y - offset.y ][ x - offset.x ]; + } + return default_floor; } }; -static void run_spot_check( const grid_overlay &test_case, const grid_overlay &expected_result ) +static void run_spot_check( const grid_overlay &test_case, const grid_overlay &expected, + bool fov_3d ) { - float seen_squares[ MAPSIZE * SEEY ][ MAPSIZE * SEEX ] = {{ 0 }}; - float transparency_cache[ MAPSIZE * SEEY ][ MAPSIZE * SEEX ] = {{ 0 }}; - - for( int y = 0; y < static_cast( sizeof( transparency_cache ) / - sizeof( transparency_cache[0] ) ); ++y ) { - for( int x = 0; x < static_cast( sizeof( transparency_cache[0] ) / - sizeof( transparency_cache[0][0] ) ); ++x ) { - transparency_cache[ y ][ x ] = test_case.get_global( x, y ); + // Reminder to not trigger 2D shadowcasting on 3D use cases. + if( !fov_3d ) { + REQUIRE( test_case.depth() == 1 ); + } + level_cache *caches[OVERMAP_LAYERS]; + std::array seen_squares; + std::array transparency_cache; + std::array floor_cache; + + const int upper_bound = fov_3d ? OVERMAP_LAYERS : 12; + for( int z = fov_3d ? 0 : 11; z < upper_bound; ++z ) { + caches[z] = new level_cache(); + seen_squares[z] = &caches[z]->seen_cache; + transparency_cache[z] = &caches[z]->transparency_cache; + floor_cache[z] = &caches[z]->floor_cache; + for( int y = 0; y < MAPSIZE * SEEY; ++y ) { + for( int x = 0; x < MAPSIZE * SEEX; ++x ) { + caches[z]->transparency_cache[x][y] = test_case.get_global( x, y, z ); + caches[z]->floor_cache[x][y] = test_case.get_floor( x, y, z ); + } } } - castLightAll( - seen_squares, transparency_cache, ORIGIN.x, ORIGIN.y ); + if( fov_3d ) { + cast_zlight( seen_squares, + transparency_cache, floor_cache, { ORIGIN.x, ORIGIN.y, ORIGIN.z - OVERMAP_DEPTH }, 0, 1.0 ); + } else { + castLightAll( + *seen_squares[11], *transparency_cache[11], ORIGIN.x, ORIGIN.y ); - // Compares the whole grid, but out-of-bounds compares will de-facto pass. - for( int y = 0; y < expected_result.height(); ++y ) { - for( int x = 0; x < expected_result.width(); ++x ) { - INFO( "x:" << x << " y:" << y << " expected:" << expected_result.data[y][x] << " actual:" << - seen_squares[expected_result.offset.y + y][expected_result.offset.x + x] ); - if( V == expected_result.get_local( x, y ) ) { - CHECK( seen_squares[expected_result.offset.y + y][expected_result.offset.x + x] > 0 ); - } else { - CHECK( seen_squares[expected_result.offset.y + y][expected_result.offset.x + x] == 0 ); + } + bool passed = true; + std::ostringstream trans_grid; + std::ostringstream expected_grid; + std::ostringstream actual_grid; + for( int gz = expected.offset.z; gz < expected.get_max().z; ++gz ) { + for( int gy = expected.offset.y; gy < expected.get_max().y; ++gy ) { + for( int gx = expected.offset.x; gx < expected.get_max().x; ++gx ) { + trans_grid << caches[gz]->transparency_cache[gx][gy]; + expected_grid << ( expected.get_global( gx, gy, gz ) > 0 ? 'V' : 'O' ); + actual_grid << ( ( *seen_squares[gz] )[gx][gy] > 0 ? 'V' : 'O' ); + if( V == expected.get_global( gx, gy, gz ) && ( *seen_squares[gz] )[gx][gy] == 0 ) { + passed = false; + } else if( O == expected.get_global( gx, gy, gz ) && + ( *seen_squares[gz] )[gx][gy] > 0 ) { + passed = false; + } } + trans_grid << '\n'; + expected_grid << '\n'; + actual_grid << '\n'; } + trans_grid << '\n'; + expected_grid << '\n'; + actual_grid << '\n'; + } + + for( int z = fov_3d ? 0 : 11; z < upper_bound; ++z ) { + delete caches[z]; } + + CAPTURE( fov_3d ); + INFO( "transparency:\n" << trans_grid.str() ); + INFO( "actual:\n" << actual_grid.str() ); + INFO( "expected:\n" << expected_grid.str() ); + CHECK( passed ); } TEST_CASE( "shadowcasting_slope_inversion_regression_test", "[shadowcasting]" ) { grid_overlay test_case( { 7, 8 }, LIGHT_TRANSPARENCY_CLEAR ); - test_case.data = { - {T, T, T, T, T, T, T, T, T, T}, - {T, O, T, T, T, T, T, T, T, T}, - {T, O, T, T, T, T, T, T, T, T}, - {T, O, O, T, O, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, O, T}, - {T, T, T, T, T, T, O, T, O, T}, - {T, T, T, T, T, T, O, O, O, T}, - {T, T, T, T, T, T, T, T, T, T} + test_case.data = { { + {T, T, T, T, T, T, T, T, T, T}, + {T, O, T, T, T, T, T, T, T, T}, + {T, O, T, T, T, T, T, T, T, T}, + {T, O, O, T, O, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, O, T}, + {T, T, T, T, T, T, O, T, O, T}, + {T, T, T, T, T, T, O, O, O, T}, + {T, T, T, T, T, T, T, T, T, T} + } }; grid_overlay expected_results( { 7, 8 }, LIGHT_TRANSPARENCY_CLEAR ); - expected_results.data = { - {O, O, O, V, V, V, V, V, V, V}, - {O, V, V, O, V, V, V, V, V, V}, - {O, O, V, V, V, V, V, V, V, V}, - {O, O, V, V, V, V, V, V, V, V}, - {O, O, V, V, V, V, V, V, V, V}, - {O, O, O, V, V, V, V, V, V, O}, - {O, O, O, O, V, V, V, V, V, O}, - {O, O, O, O, O, V, V, V, V, O}, - {O, O, O, O, O, O, V, X, V, O}, - {O, O, O, O, O, O, V, V, V, O}, - {O, O, O, O, O, O, O, O, O, O} + expected_results.data = { { + {O, O, O, V, V, V, V, V, V, V}, + {O, V, V, O, V, V, V, V, V, V}, + {O, O, V, V, V, V, V, V, V, V}, + {O, O, V, V, V, V, V, V, V, V}, + {O, O, V, V, V, V, V, V, V, V}, + {O, O, O, V, V, V, V, V, V, O}, + {O, O, O, O, V, V, V, V, V, O}, + {O, O, O, O, O, V, V, V, V, O}, + {O, O, O, O, O, O, V, X, V, O}, + {O, O, O, O, O, O, V, V, V, O}, + {O, O, O, O, O, O, O, O, O, O} + } }; - run_spot_check( test_case, expected_results ); + run_spot_check( test_case, expected_results, true ); + run_spot_check( test_case, expected_results, false ); } TEST_CASE( "shadowcasting_pillar_behavior_cardinally_adjacent", "[shadowcasting]" ) { grid_overlay test_case( { 1, 4 }, LIGHT_TRANSPARENCY_CLEAR ); - test_case.data = { - {T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T}, - {T, T, O, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T} + test_case.data = { { + {T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T}, + {T, T, O, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T} + } }; grid_overlay expected_results( { 1, 4 }, LIGHT_TRANSPARENCY_CLEAR ); - expected_results.data = { - {V, V, V, V, V, V, V, O, O}, - {V, V, V, V, V, V, O, O, O}, - {V, V, V, V, V, O, O, O, O}, - {V, V, V, V, O, O, O, O, O}, - {V, X, V, O, O, O, O, O, O}, - {V, V, V, V, O, O, O, O, O}, - {V, V, V, V, V, O, O, O, O}, - {V, V, V, V, V, V, O, O, O}, - {V, V, V, V, V, V, V, O, O} + expected_results.data = { { + {V, V, V, V, V, V, V, O, O}, + {V, V, V, V, V, V, O, O, O}, + {V, V, V, V, V, O, O, O, O}, + {V, V, V, V, O, O, O, O, O}, + {V, X, V, O, O, O, O, O, O}, + {V, V, V, V, O, O, O, O, O}, + {V, V, V, V, V, O, O, O, O}, + {V, V, V, V, V, V, O, O, O}, + {V, V, V, V, V, V, V, O, O} + } }; - run_spot_check( test_case, expected_results ); + run_spot_check( test_case, expected_results, true ); + run_spot_check( test_case, expected_results, false ); } TEST_CASE( "shadowcasting_pillar_behavior_2_1_diagonal_gap", "[shadowcasting]" ) { grid_overlay test_case( { 1, 1 }, LIGHT_TRANSPARENCY_CLEAR ); - test_case.data = { - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, O, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T} + test_case.data = { { + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, O, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T} + } }; grid_overlay expected_results( { 1, 1 }, LIGHT_TRANSPARENCY_CLEAR ); - expected_results.data = { - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, X, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, O, O, O, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, O, O, O, O, O, O, O, V, V, V, V, V}, - {V, V, V, V, V, V, V, O, O, O, O, O, O, O, O, O, O, O}, - {V, V, V, V, V, V, V, V, O, O, O, O, O, O, O, O, O, O}, - {V, V, V, V, V, V, V, V, V, O, O, O, O, O, O, O, O, O}, - {V, V, V, V, V, V, V, V, V, V, O, O, O, O, O, O, O, O}, + expected_results.data = { { + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, X, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, O, O, O, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, O, O, O, O, O, O, O, V, V, V, V, V}, + {V, V, V, V, V, V, V, O, O, O, O, O, O, O, O, O, O, O}, + {V, V, V, V, V, V, V, V, O, O, O, O, O, O, O, O, O, O}, + {V, V, V, V, V, V, V, V, V, O, O, O, O, O, O, O, O, O}, + {V, V, V, V, V, V, V, V, V, V, O, O, O, O, O, O, O, O}, + } }; - run_spot_check( test_case, expected_results ); + run_spot_check( test_case, expected_results, true ); + run_spot_check( test_case, expected_results, false ); } TEST_CASE( "shadowcasting_vision_along_a_wall", "[shadowcasting]" ) { grid_overlay test_case( { 8, 2 }, LIGHT_TRANSPARENCY_CLEAR ); - test_case.data = { - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, - {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T} + test_case.data = { { + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T} + } }; grid_overlay expected_results( { 8, 2 }, LIGHT_TRANSPARENCY_CLEAR ); + expected_results.data = { { + {O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O}, + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, V, V, X, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, + {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V} + } + }; + + run_spot_check( test_case, expected_results, true ); + run_spot_check( test_case, expected_results, false ); +} + +TEST_CASE( "shadowcasting_edgewise_wall_view", "[shadowcasting]" ) +{ + grid_overlay test_case( { 1, 2 }, LIGHT_TRANSPARENCY_CLEAR ); + test_case.data = { { + {T, T, O, T, T, T, T}, + {T, T, O, T, T, T, T}, + {T, T, O, O, O, T, T}, + {T, T, T, T, T, T, T}, + {T, T, T, T, T, T, T} + } + }; + + grid_overlay expected_results( { 1, 2 }, LIGHT_TRANSPARENCY_CLEAR ); + expected_results.data = { { + {V, V, V, O, O, O, O}, + {V, V, V, O, O, O, O}, + {V, X, V, O, O, O, O}, + {V, V, V, V, O, O, O}, + {V, V, V, V, V, O, O}, + {V, V, V, V, V, V, O} + } + }; + + run_spot_check( test_case, expected_results, true ); + run_spot_check( test_case, expected_results, false ); +} + +TEST_CASE( "shadowcasting_opaque_floors", "[shadowcasting]" ) +{ + grid_overlay test_case( { 2, 2, 1 }, LIGHT_TRANSPARENCY_CLEAR ); + test_case.data = { + { + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T} + }, + { + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T} + }, + { + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T} + } + }; + + grid_overlay expected_results( { 2, 2, 1 }, LIGHT_TRANSPARENCY_CLEAR ); expected_results.data = { - {O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O}, - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, V, V, X, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}, - {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V} + { + {O, O, O, O, O}, + {O, O, O, O, O}, + {O, O, O, O, O}, + {O, O, O, O, O}, + {O, O, O, O, O} + }, + { + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, X, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V} + }, + { + {O, O, O, O, O}, + {O, O, O, O, O}, + {O, O, O, O, O}, + {O, O, O, O, O}, + {O, O, O, O, O} + } + }; + + run_spot_check( test_case, expected_results, true ); +} + +TEST_CASE( "shadowcasting_transparent_floors", "[shadowcasting]" ) +{ + grid_overlay test_case( { 2, 2, 1 }, LIGHT_TRANSPARENCY_CLEAR ); + test_case.data = { + { + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T} + }, + { + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T} + }, + { + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T}, + {T, T, T, T, T} + } + }; + test_case.default_floor = false; + + grid_overlay expected_results( { 2, 2, 1 }, LIGHT_TRANSPARENCY_CLEAR ); + expected_results.data = { + { + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V} + }, + { + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, X, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V} + }, + { + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V}, + {V, V, V, V, V} + } }; - run_spot_check( test_case, expected_results ); + run_spot_check( test_case, expected_results, true ); } // Some random edge cases aren't matching. diff --git a/tests/vision_test.cpp b/tests/vision_test.cpp index 51272ac5b1854..64601c2344ee1 100644 --- a/tests/vision_test.cpp +++ b/tests/vision_test.cpp @@ -224,7 +224,6 @@ struct vision_test_case { std::vector setup; std::vector expected_results; calendar time; - bool test_3d; static void transpose( std::vector &v ) { if( v.empty() ) { @@ -284,9 +283,7 @@ struct vision_test_case { } void test_all() const { - // Disabling 3d tests for now since 3d sight casting is actually - // different (it sees round corners more). - if( test_3d ) { + { INFO( "using 3d casting" ); fov_3d = true; test_all_transformations(); @@ -325,8 +322,7 @@ TEST_CASE( "vision_daylight", "[shadowcasting][vision]" ) "444", "444", }, - midday, - true + midday }; t.test_all(); @@ -345,8 +341,7 @@ TEST_CASE( "vision_day_indoors", "[shadowcasting][vision]" ) "111", "111", }, - midday, - true + midday }; t.test_all(); @@ -369,8 +364,7 @@ TEST_CASE( "vision_light_shining_in", "[shadowcasting][vision]" ) "1144444444", "1144444444", }, - midday, - false // 3D FOV gives different results here due to it seeing round corners more + midday }; t.test_all(); @@ -387,8 +381,7 @@ TEST_CASE( "vision_no_lights", "[shadowcasting][vision]" ) "111", "111", }, - midnight, - true + midnight }; t.test_all(); @@ -407,8 +400,7 @@ TEST_CASE( "vision_utility_light", "[shadowcasting][vision]" ) "444", "444", }, - midnight, - true + midnight }; t.test_all(); @@ -427,8 +419,7 @@ TEST_CASE( "vision_wall_obstructs_light", "[shadowcasting][vision]" ) "111", "111", }, - midnight, - true + midnight }; t.test_all(); @@ -451,8 +442,7 @@ TEST_CASE( "vision_wall_can_be_lit_by_player", "[shadowcasting][vision]" ) "44", "66", }, - midnight, - true + midnight }; t.test_all(); @@ -481,8 +471,7 @@ TEST_CASE( "vision_see_wall_in_moonlight", "[shadowcasting][vision]" ) "111", "111", }, - DAYS( days_till_full_moon ), - true + DAYS( days_till_full_moon ) }; t.test_all();