diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index 256b3f178dbc0..5f18161702733 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -1,11 +1,21 @@ #include "shadowcasting.h" -#include +#include #include "enums.h" #include "fragment_cloud.h" // IWYU pragma: keep #include "line.h" +template +struct span { + // TODO: Make these fixed-point or byte/byte pairs + float start_major; + float end_major; + float start_minor; + float end_minor; + T cumulative_value; +}; + // Add defaults for when method is invoked for the first time. template &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 ); + const T numerator = 1.0f ); template &input_arrays, const std::array &floor_caches, const tripoint &offset, const int offset_distance, - const T numerator, const int row, + const T numerator ) +/* old recursion args 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; + const float radius = 60.0f - offset_distance; constexpr int min_z = -OVERMAP_DEPTH; constexpr int max_z = OVERMAP_HEIGHT; @@ -50,235 +55,314 @@ void cast_zlight_segment( static constexpr tripoint origin( 0, 0, 0 ); tripoint delta( 0, 0, 0 ); tripoint current( 0, 0, 0 ); - for( int distance = row; distance <= radius; distance++ ) { + // TODO: More optimal data structure. + // 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 after it in the list. + std::list spans = { { 0.0, 1.0, 0.0, 1.0, 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.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 ) { + for( auto it = spans.begin(); it != spans.end(); ++it ) { + span &this_span = *it; + // 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( this_span.start_major > leading_edge_major ) { + // Current span has a higher z-value, + // jump to next iteration to catch up. continue; - } else if( end_minor < trailing_edge_minor ) { + } 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; } - 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; + 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; + // Shadowcasting sweeps from the most extreme edge of the octant to the cardinal + // XXXX + // <--- + // XXX + // <-- + // XX + // <- + // X + // @ + // + // Leading edge -> +- + // |+| <- Center of tile + // -+ <- Trailing edge + // <------ Direction of sweep + // Use corners of given tile as above to determine angles of + // leading and trailing edges being considered. + 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 ) || this_span.start_minor > leading_edge_minor ) { + // Current tile comes before 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; } - } else if( current.z > offset.z ) { - if( ( *floor_caches[z_index] )[current.x][current.y] ) { - floor_block = true; - new_transparency = LIGHT_TRANSPARENCY_SOLID; + + 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; - } + 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 ); + const int dist = rl_dist( origin, delta ) + offset_distance; + last_intensity = calc( numerator, this_span.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( !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( !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; - } + if( new_transparency == current_transparency ) { + // All in order, no need to split the span. + 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 + // We split the span into up to 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). This might be empty. + // B is already-processed tiles from current row. This must exist. + // C is remainder of current row. This can maybe be empty. + // D is not yet processed row(s). Might be empty. + // A, B and D have the previous transparency, C has the new transparency, + // which might be opaque. + // One we processed fully in 2D and only need to extend in last D + // Only emit a new span horizontally if previous span was not opaque. + // If end_minor is <= trailing_edge_minor, A, B, C remain one span and + // D becomes a new span if present. + // If this is the first row processed in the current span, there is no A span. + // If this is the last row processed, there is no D span. + // If check returns false A, B and D are now opaque and + // will have no spans emitted. + if( check( current_transparency, last_intensity ) ) { + T next_cumulative_transparency = accumulate( this_span.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 + // In this case blocks A, B and C above remain as a single span. + // TODO: verify this actually happens. + const bool merge_blocks = this_span.end_minor <= trailing_edge_minor; + if( merge_blocks ) { + if( this_span.end_major > leading_edge_major ) { + // We have some span left, so insert it after the current one. + spans.emplace( std::next( it ), + leading_edge_major, this_span.end_major, + this_span.start_minor, this_span.end_minor, + next_cumulative_transparency ); + // Truncate this_span to the end of the current block. + this_span.end_major = leading_edge_major; + } + // This span is done. + break; + } + // If this is true, we don't have to emit an 'A' span. + // If this doesn't work, maybe track first row of a span with a bool + const bool first_row = trailing_edge_major <= this_span.start_major; + if( !first_row ) { + spans.emplace( std::next( it ), + major_mid, this_span.end_major, + this_span.start_minor, this_span / end_minor, + next_cumulative_transparency ); + // All we do to A is truncate it. + this_span.end_major = trailing_edge_major; + // Then make the current span the one we just inserted. + it++; + } + // One way or the other, the current span is now BCD. + // First handle D if it exists. + if( this_span.end_major > leading_edge_major ) { + // We may have some span left, so insert it after the current one. + spans.emplace( std::next( it ), + leading_edge_major, this_span.end_major, + this_span.start_minor, this_span.end_minor, + next_cumulative_transparency ); + // Truncate this_span to the end of the current block. + this_span.end_major = leading_edge_major; + } + + // trailing_edge_major can be less than start_major + const float trailing_clipped = std::max( trailing_edge_major, this_span.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, - major_mid, leading_edge_major, start_minor, trailing_edge_minor, + 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; } - // 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 ); + start_major = leading_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( !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 ) ) { - start_major = leading_edge_major; + // 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 ); } - - 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 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 ); + } -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 ); + // 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 );