From de6a788810d3ed28891475a65072c97300580478 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 15 May 2019 06:31:55 +0000 Subject: [PATCH 01/17] Extend shadowcasting test to 3D --- tests/shadowcasting_test.cpp | 298 +++++++++++++++++++++-------------- 1 file changed, 179 insertions(+), 119 deletions(-) diff --git a/tests/shadowcasting_test.cpp b/tests/shadowcasting_test.cpp index a6f10bfcc5756..0e88dc439efa8 100644 --- a/tests/shadowcasting_test.cpp +++ b/tests/shadowcasting_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -412,11 +413,11 @@ 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; + tripoint offset; float default_value; // origin_offset is specified as the coordinates of the "camera" within the overlay. @@ -424,185 +425,244 @@ struct grid_overlay { 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 ]; - } }; -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; + + int z = fov_3d ? 0 : 11; + const int upper_bound = fov_3d ? OVERMAP_LAYERS : 12; + for( ; 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 ); + } } } - 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'; + delete caches[gz]; } + 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} + 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 ); + run_spot_check( test_case, expected_results, true ); + run_spot_check( test_case, expected_results, false ); } // Some random edge cases aren't matching. From 65da1d9d18835426543b18487e5ff903f615b8bf Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 26 May 2019 18:14:33 +0000 Subject: [PATCH 02/17] Add some 3D tests. --- tests/shadowcasting_test.cpp | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/tests/shadowcasting_test.cpp b/tests/shadowcasting_test.cpp index 0e88dc439efa8..9f50490e46ba2 100644 --- a/tests/shadowcasting_test.cpp +++ b/tests/shadowcasting_test.cpp @@ -417,8 +417,10 @@ const tripoint ORIGIN( 65, 65, 11 ); struct grid_overlay { 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 ) { @@ -457,6 +459,15 @@ struct grid_overlay { } return default_value; } + 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, @@ -481,6 +492,7 @@ static void run_spot_check( const grid_overlay &test_case, const grid_overlay &e 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 ); } } } @@ -665,6 +677,144 @@ TEST_CASE( "shadowcasting_vision_along_a_wall", "[shadowcasting]" ) 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, 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, true ); +} + // Some random edge cases aren't matching. TEST_CASE( "shadowcasting_runoff", "[.]" ) { From 630c0a4f5a400ebdb5408f7a3e565f2b5258a4b4 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 29 May 2019 01:40:26 +0000 Subject: [PATCH 03/17] Extract cast_zlight to a dedicated code module --- src/lightmap.cpp | 277 ---------------------------------------- src/shadowcasting.cpp | 284 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 277 deletions(-) create mode 100644 src/shadowcasting.cpp 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" + +// 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 ); From 102dd6cb18e06add913636b67089f065a8b4818d Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 2 Jun 2019 17:42:02 +0000 Subject: [PATCH 04/17] WIP de-recursify shadowcasting --- src/shadowcasting.cpp | 348 +++++++++++++++++++++++------------------- 1 file changed, 194 insertions(+), 154 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index 256b3f178dbc0..4cf3effa334f4 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -1,11 +1,26 @@ #include "shadowcasting.h" -#include +#include #include "enums.h" #include "fragment_cloud.h" // IWYU pragma: keep #include "line.h" +template +struct span { + span( const float &s_major, const float &e_major, + const float &s_minor, const float &e_minor, + const T &value ) : + start_major( s_major ), end_major( e_major ), start_minor( s_minor ), end_minor( e_minor ), + cumulative_value( value ) {} + // 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, - float start_major, const float end_major, - float start_minor, const float end_minor, - T cumulative_transparency ) + const T numerator ) { - 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,169 +55,205 @@ 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_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( !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 = this_span.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 - 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 ); + T next_cumulative_transparency = accumulate( this_span.cumulative_value, + current_transparency, distance ); + // 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 must exist. + // 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 and B are opaque and have no spans. + if( check( current_transparency, last_intensity ) ) { + // Emit the A span if it's present. + if( trailing_edge_major > this_span.start_major ) { + // This is BCD + spans.emplace( std::next( it ), + trailing_edge_major, this_span.end_major, + this_span.start_minor, this_span.end_minor, + this_span.cumulative_value ); + // 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 B. + const float major_mid = std::max( this_span.start_major, + trailing_edge_major ); + const float major_mid_end = std::min( this_span.end_major, + leading_edge_major ); + spans.emplace( it, major_mid, major_mid_end, + this_span.start_minor, leading_edge_minor, + next_cumulative_transparency ); + // Overwrite new_start_minor since previous tile is transparent. + new_start_minor = trailing_edge_minor; } + // Current span is BCD, but B either doesn't exist or is handled. + // Split off D if it exists. + if( leading_edge_major < this_span.end_major ) { + spans.emplace( std::next( it ), + leading_edge_major, this_span.end_major, + this_span.start_minor, this_span.end_minor, + this_span.cumulative_value ); + } + // Truncate this_span to the current block. + this_span.end_major = std::min( this_span.end_major, leading_edge_major ); + this_span.start_major = std::max( this_span.start_major, trailing_edge_major ); + // 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. + this_span.start_minor = new_start_minor; + this_span.cumulative_value = next_cumulative_transparency; + + new_start_minor = leading_edge_minor; + current_transparency = new_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 ); + this_span.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. + this_span.cumulative_value = accumulate( this_span.cumulative_value, + 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 ); } } @@ -223,8 +264,7 @@ 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 ) -{ + 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 ); @@ -274,11 +314,11 @@ template void cast_zlight &output_caches, const std::array &input_arrays, const std::array &floor_caches, - const tripoint &origin, const int offset_distance, const float numerator ); + 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 ); + const tripoint & origin, const int offset_distance, const fragment_cloud numerator ); From 54611259eec28855dfe876f42f2fb6389f3a293a Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 29 Jun 2019 22:00:46 +0200 Subject: [PATCH 05/17] Remove unnecessary floats and fix bugs Replace floats with new zlight_slope struct. This now passes 2 of the tests that the old recursive algorithm was failing, but encounters an infinite loop in some cases without the fuzzing that seems to have been hiding a major bug. Fix a few other minor bugs. --- src/shadowcasting.cpp | 173 ++++++++++++++++++++++++++---------------- 1 file changed, 107 insertions(+), 66 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index 4cf3effa334f4..3dbdf4fca8630 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -6,18 +6,59 @@ #include "fragment_cloud.h" // IWYU pragma: keep #include "line.h" +struct slope { + slope( int rise, int run ) { + // Ensure run is always positive for the comparison operators + this->run = abs( run ); + if( run < 0 ) { + this->rise = -rise; + } else { + this->rise = rise; + } + } + + int run; + int rise; +}; + +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 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 !( lhs == rhs ); +} + template -struct span { - span( const float &s_major, const float &e_major, - const float &s_minor, const float &e_minor, +struct span { + span( const slope &s_major, const slope &e_major, + const slope &s_minor, const slope &e_minor, const T &value ) : start_major( s_major ), end_major( e_major ), start_minor( s_minor ), end_minor( e_minor ), cumulative_value( value ) {} - // TODO: Make these fixed-point or byte/byte pairs - float start_major; - float end_major; - float start_minor; - float end_minor; + slope start_major; + slope end_major; + slope start_minor; + slope end_minor; T cumulative_value; }; @@ -44,12 +85,12 @@ void cast_zlight_segment( const tripoint &offset, const int offset_distance, const T numerator ) { - const float radius = 60.0f - offset_distance; + const int radius = 60 - offset_distance; constexpr int min_z = -OVERMAP_DEPTH; constexpr int max_z = OVERMAP_HEIGHT; - float new_start_minor = 1.0f; + slope new_start_minor( 1, 1 ); T last_intensity = 0.0; static constexpr tripoint origin( 0, 0, 0 ); @@ -59,28 +100,32 @@ void cast_zlight_segment( // 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 } }; + 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.y = distance; bool started_block = false; - T current_transparency = 0.0f; + T current_transparency; - for( auto it = spans.begin(); it != spans.end(); ++it ) { - span &this_span = *it; + for( auto this_span = spans.begin(); this_span != spans.end(); ++this_span ) { // 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 ); + 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.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 ) { + } 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.end_major < trailing_edge_major ) { + } 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; @@ -91,30 +136,30 @@ void cast_zlight_segment( 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 + // Shadowcasting sweeps from the cardinal to the most extreme edge of the octant // XXXX - // <--- + // ---> // XXX - // <-- + // --> // XX - // <- + // -> // X // @ // - // Leading edge -> +- - // |+| <- Center of tile - // -+ <- Trailing edge - // <------ Direction of sweep + // 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. - float trailing_edge_minor = ( delta.x - 0.5f ) / ( delta.y + 0.5f ); - float leading_edge_minor = ( delta.x + 0.5f ) / ( delta.y - 0.5f ); + 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.y >= 0 && current.x < MAPSIZE_X && - current.y < MAPSIZE_Y ) || this_span.start_minor > leading_edge_minor ) { + 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 ) { + } else if( this_span->end_minor < trailing_edge_minor ) { // Current tile is after the span we're considering, continue to next row. break; } @@ -142,7 +187,7 @@ void cast_zlight_segment( } const int dist = rl_dist( origin, delta ) + offset_distance; - last_intensity = calc( numerator, this_span.cumulative_value, dist ); + last_intensity = calc( numerator, this_span->cumulative_value, dist ); if( !floor_block ) { ( *output_caches[z_index] )[current.x][current.y] = @@ -152,8 +197,6 @@ void cast_zlight_segment( 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 = this_span.start_minor; started_span = true; } @@ -163,15 +206,15 @@ void cast_zlight_segment( continue; } - T next_cumulative_transparency = accumulate( this_span.cumulative_value, - current_transparency, distance ); + const T next_cumulative_transparency = accumulate( this_span->cumulative_value, + current_transparency, distance ); // 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 | - // +---+---+ <- ??? + // +---+---+ <- leading edge major // | B | C | - // +---+---+ <- major mid + // +---+---+ <- trailing edge major // | A | // +-------+ <- start major // ^ ^ @@ -192,51 +235,48 @@ void cast_zlight_segment( // If check returns false, A and B are opaque and have no spans. if( check( current_transparency, last_intensity ) ) { // Emit the A span if it's present. - if( trailing_edge_major > this_span.start_major ) { - // This is BCD - spans.emplace( std::next( it ), - trailing_edge_major, this_span.end_major, - this_span.start_minor, this_span.end_minor, - this_span.cumulative_value ); + if( trailing_edge_major > this_span->start_major ) { + // Place BCD next in the list + spans.emplace( std::next( this_span ), + trailing_edge_major, this_span->end_major, + this_span->start_minor, this_span->end_minor, + this_span->cumulative_value ); // All we do to A is truncate it. - this_span.end_major = trailing_edge_major; + this_span->end_major = trailing_edge_major; // Then make the current span the one we just inserted. - it++; + ++this_span; } // One way or the other, the current span is now BCD. // First handle B. - const float major_mid = std::max( this_span.start_major, - trailing_edge_major ); - const float major_mid_end = std::min( this_span.end_major, - leading_edge_major ); - spans.emplace( it, major_mid, major_mid_end, - this_span.start_minor, leading_edge_minor, + spans.emplace( this_span, this_span->start_major, leading_edge_major, + this_span->start_minor, trailing_edge_minor, next_cumulative_transparency ); // Overwrite new_start_minor since previous tile is transparent. new_start_minor = trailing_edge_minor; } // Current span is BCD, but B either doesn't exist or is handled. // Split off D if it exists. - if( leading_edge_major < this_span.end_major ) { - spans.emplace( std::next( it ), - leading_edge_major, this_span.end_major, - this_span.start_minor, this_span.end_minor, - this_span.cumulative_value ); + if( leading_edge_major < this_span->end_major ) { + // Infinite loop where the same D gets split off every iteration currently + spans.emplace( std::next( this_span ), + leading_edge_major, this_span->end_major, + this_span->start_minor, this_span->end_minor, + this_span->cumulative_value ); } // Truncate this_span to the current block. - this_span.end_major = std::min( this_span.end_major, leading_edge_major ); - this_span.start_major = std::max( this_span.start_major, trailing_edge_major ); + this_span->start_major = trailing_edge_major; + this_span->end_major = leading_edge_major; // 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. - this_span.start_minor = new_start_minor; - this_span.cumulative_value = next_cumulative_transparency; + this_span->start_minor = new_start_minor; + this_span->cumulative_value = next_cumulative_transparency; new_start_minor = leading_edge_minor; current_transparency = new_transparency; } if( !check( current_transparency, last_intensity ) ) { - this_span.start_major = leading_edge_major; + this_span->start_major = leading_edge_major; } } @@ -251,8 +291,8 @@ void cast_zlight_segment( break; } // Cumulative average of the values encountered. - this_span.cumulative_value = accumulate( this_span.cumulative_value, - current_transparency, distance ); + this_span->cumulative_value = accumulate( this_span->cumulative_value, + current_transparency, distance ); } } } @@ -264,7 +304,8 @@ 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 ) { + 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 ); @@ -314,11 +355,11 @@ template void cast_zlight &output_caches, const std::array &input_arrays, const std::array &floor_caches, - const tripoint & origin, const int offset_distance, const float numerator ); + 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 ); + const tripoint &origin, const int offset_distance, const fragment_cloud numerator ); From aa42e7121882ab8477243a1823828afbf31896ae Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Thu, 4 Jul 2019 19:09:54 +0200 Subject: [PATCH 06/17] More bugfixes and kill the inifinte loop This is the best way I could find to fix the infinite loop without creating tons of artifacts. --- src/shadowcasting.cpp | 80 +++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index 3dbdf4fca8630..1d8d9630e1042 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -17,8 +17,8 @@ struct slope { } } - int run; int rise; + int run; }; inline bool operator<( const slope &lhs, const slope &rhs ) @@ -52,14 +52,15 @@ template struct span { span( const slope &s_major, const slope &e_major, const slope &s_minor, const slope &e_minor, - const T &value ) : + 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 ) {} + 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; }; // Add defaults for when method is invoked for the first time. @@ -110,10 +111,10 @@ void cast_zlight_segment( // possibly splitting them. for( int distance = 1; distance <= radius; distance++ ) { delta.y = distance; - bool started_block = false; - T current_transparency; + T current_transparency = 0.0f; - for( auto this_span = spans.begin(); this_span != spans.end(); ++this_span ) { + for( auto this_span = spans.begin(); this_span != spans.end(); ) { + bool started_block = false; // 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 ); @@ -125,6 +126,15 @@ void cast_zlight_segment( // 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. @@ -234,38 +244,38 @@ void cast_zlight_segment( // If this is the last row processed, there is no D span. // If check returns false, A and B are opaque and have no spans. if( check( current_transparency, last_intensity ) ) { - // Emit the A span if it's present. + // Emit the A span if present, placing it before the current span in the list if( trailing_edge_major > this_span->start_major ) { - // Place BCD next in the list - spans.emplace( std::next( this_span ), - trailing_edge_major, this_span->end_major, + spans.emplace( this_span, + this_span->start_major, trailing_edge_major, this_span->start_minor, this_span->end_minor, - this_span->cumulative_value ); - // 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. - ++this_span; + next_cumulative_transparency ); + } + + // Emit the B span if present, placing it before the current span in the list + if( trailing_edge_minor > this_span->start_minor ) { + spans.emplace( this_span, + std::max( this_span->start_major, trailing_edge_major ), + std::min( this_span->end_major, leading_edge_major ), + this_span->start_minor, trailing_edge_minor, + next_cumulative_transparency ); } - // One way or the other, the current span is now BCD. - // First handle B. - spans.emplace( this_span, this_span->start_major, leading_edge_major, - this_span->start_minor, trailing_edge_minor, - next_cumulative_transparency ); + // Overwrite new_start_minor since previous tile is transparent. new_start_minor = trailing_edge_minor; } - // Current span is BCD, but B either doesn't exist or is handled. - // Split off D if it exists. + + // Emit the D span if present, placing it after the current span in the list if( leading_edge_major < this_span->end_major ) { - // Infinite loop where the same D gets split off every iteration currently spans.emplace( std::next( this_span ), leading_edge_major, this_span->end_major, this_span->start_minor, this_span->end_minor, - this_span->cumulative_value ); + this_span->cumulative_value, true ); } + // Truncate this_span to the current block. - this_span->start_major = trailing_edge_major; - this_span->end_major = leading_edge_major; + this_span->start_major = std::max( this_span->start_major, trailing_edge_major ); + this_span->end_major = std::min( this_span->end_major, leading_edge_major ); // 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. this_span->start_minor = new_start_minor; @@ -275,6 +285,8 @@ void cast_zlight_segment( current_transparency = new_transparency; } + // 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( !check( current_transparency, last_intensity ) ) { this_span->start_major = leading_edge_major; } @@ -283,16 +295,16 @@ void cast_zlight_segment( 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 ) ) { + this_span = spans.erase( this_span ); + } else if( !check( current_transparency, last_intensity ) ) { // If we reach the end of the span with terrain being opaque, we don't iterate further. - break; + 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; } - // Cumulative average of the values encountered. - this_span->cumulative_value = accumulate( this_span->cumulative_value, - current_transparency, distance ); } } } From 8f898974f508a91ed34f50a58785670d51ecdad5 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 5 Jul 2019 11:37:56 +0200 Subject: [PATCH 07/17] Add more comments and clean up formatting - Add casting direction diagrams - Remove unnecessary declaration --- src/shadowcasting.cpp | 105 ++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index 1d8d9630e1042..af2859fdb0cfd 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -63,18 +63,6 @@ struct span { bool skip_first_row; }; -// 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 ); - template> spans = { { slope( 0, 1 ), slope( 1, 1 ), slope( 0, 1 ), slope( 1, 1 ), @@ -119,8 +108,9 @@ void cast_zlight_segment( 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.x * 00 + delta.y * 00 + delta.z * zz; + current.z = offset.z + delta.z * zz; 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, @@ -165,9 +155,12 @@ void cast_zlight_segment( 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.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. + 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. @@ -175,8 +168,9 @@ void cast_zlight_segment( } 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. + // that tile is actually invisible to us. bool floor_block = false; if( current.z < offset.z ) { if( z_index < ( OVERMAP_LAYERS - 1 ) && @@ -267,6 +261,9 @@ void cast_zlight_segment( // Emit the D span if present, placing it after the current span in the list if( leading_edge_major < this_span->end_major ) { + // Pass true to the span constructor to set skip_first_row to true + // This prevents the same row we are currently checking being check by the + // new D span spans.emplace( std::next( this_span ), leading_edge_major, this_span->end_major, this_span->start_minor, this_span->end_minor, @@ -276,6 +273,7 @@ void cast_zlight_segment( // Truncate this_span to the current block. this_span->start_major = std::max( this_span->start_major, trailing_edge_major ); this_span->end_major = std::min( this_span->end_major, leading_edge_major ); + // 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. this_span->start_minor = new_start_minor; @@ -294,10 +292,13 @@ void cast_zlight_segment( 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 + // 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( !check( 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. @@ -318,45 +319,89 @@ void cast_zlight( const std::array &floor_caches, const tripoint &origin, const int offset_distance, const T numerator ) { - // Down + // Down lateral + + // . + // .. + // @.. 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>( + // Up lateral + + // . + // .. + // @.. + 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>( + // ... + // .. + // @ + 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 ); } From 51e41490a086ad59abd5eefc743900319f822bc5 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 5 Jul 2019 13:19:35 +0200 Subject: [PATCH 08/17] Cast zlight straight up and down This commit adds a new cast_vertical_zlight_segment function with different iteration logic than the now renamed cast_horizontal_zlight_segment. This duplicates a lot of code, but imo is worth it for the readablity gains over adding a lot more contitonals to the iteration logic to handle zx and zy template parameters properly. --- src/shadowcasting.cpp | 319 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 302 insertions(+), 17 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index af2859fdb0cfd..efbbe8361aca5 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -67,7 +67,7 @@ template -void cast_zlight_segment( +void cast_horizontal_zlight_segment( const std::array &output_caches, const std::array &input_arrays, const std::array &floor_caches, @@ -310,6 +310,253 @@ void cast_zlight_segment( } } +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_span = 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_span ) { + // Need to reset minor slope, because we're starting a new line + new_start_minor = leading_edge_minor; + started_span = true; + } + + if( new_transparency == current_transparency ) { + // All in order, no need to split the span. + new_start_minor = leading_edge_minor; + continue; + } + + const T next_cumulative_transparency = accumulate( this_span->cumulative_value, + current_transparency, distance ); + // 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 | + // +---+---+ <- leading edge major + // | B | C | + // +---+---+ <- 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 must exist. + // C is remainder of current row. This must exist. + // 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 and B are opaque and have no spans. + if( check( 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 ); + } + + // Emit the B span if present, placing it before the current span in the list + if( trailing_edge_minor > this_span->start_minor ) { + spans.emplace( this_span, + std::max( this_span->start_major, trailing_edge_major ), + std::min( this_span->end_major, leading_edge_major ), + this_span->start_minor, trailing_edge_minor, + next_cumulative_transparency ); + } + + // Overwrite new_start_minor since previous tile is transparent. + new_start_minor = trailing_edge_minor; + } + + // Emit the D span if present, placing it after the current span in the list + if( leading_edge_major < this_span->end_major ) { + // Pass true to the span constructor to set skip_first_row to true + // This prevents the same row we are currently checking being check by the + // new D span + spans.emplace( std::next( this_span ), + leading_edge_major, this_span->end_major, + this_span->start_minor, this_span->end_minor, + this_span->cumulative_value, true ); + } + + // Truncate this_span to the current block. + this_span->start_major = std::max( this_span->start_major, trailing_edge_major ); + this_span->end_major = std::min( this_span->end_major, leading_edge_major ); + + // 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. + this_span->start_minor = new_start_minor; + this_span->cumulative_value = next_cumulative_transparency; + + new_start_minor = 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 + // since we don't need to process the current one any more. + if( !check( 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( !check( 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 @@ -324,42 +571,42 @@ void cast_zlight( // . // .. // @.. - cast_zlight_segment < 0, 1, 0, 1, 0, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_zlight_segment < -1, 0, 0, 0, -1, 0, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // Up lateral @@ -367,42 +614,80 @@ void cast_zlight( // . // .. // @.. - cast_zlight_segment < 0, 1, 0, 1, 0, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_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 > ( + cast_horizontal_zlight_segment < -1, 0, 0, 0, -1, 0, 1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + + // Straight up + + // .. + // @. + cast_vertical_zlight_segment < 1, 1, 1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // @. + // .. + cast_vertical_zlight_segment < 1, -1, 1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .. + // .@ + cast_vertical_zlight_segment < -1, 1, 1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .@ + // .. + cast_vertical_zlight_segment < -1, -1, 1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + + // Straight down + + // .. + // @. + cast_vertical_zlight_segment < 1, 1, -1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // @. + // .. + cast_vertical_zlight_segment < 1, -1, -1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .. + // .@ + cast_vertical_zlight_segment < -1, 1, -1, T, calc, check, accumulate > ( + output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + // .@ + // .. + cast_vertical_zlight_segment < -1, -1, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); } From 226059d2fcec151135b2a05b539298e65196586a Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 5 Jul 2019 20:57:22 +0200 Subject: [PATCH 09/17] Add a helper to reduce code duplication --- src/shadowcasting.cpp | 236 +++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 142 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index efbbe8361aca5..9a1adcd0d8fc3 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -63,6 +63,90 @@ struct span { 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 ) +{ + const T next_cumulative_transparency = accumulate( this_span->cumulative_value, + current_transparency, distance ); + // 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 | + // +---+---+ <- leading edge major + // | B | C | + // +---+---+ <- 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 must exist. + // C is remainder of current row. This must exist. + // 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 and B are opaque and have no spans. + if( check( 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 ); + } + + // Emit the B span if present, placing it before the current span in the list + if( trailing_edge_minor > this_span->start_minor ) { + spans.emplace( this_span, + std::max( this_span->start_major, trailing_edge_major ), + std::min( this_span->end_major, leading_edge_major ), + this_span->start_minor, trailing_edge_minor, + next_cumulative_transparency ); + } + + // Overwrite new_start_minor since previous tile is transparent. + new_start_minor = trailing_edge_minor; + } + + // Emit the D span if present, placing it after the current span in the list + if( leading_edge_major < this_span->end_major ) { + // Pass true to the span constructor to set skip_first_row to true + // This prevents the same row we are currently checking being check by the + // new D span + spans.emplace( std::next( this_span ), + leading_edge_major, this_span->end_major, + this_span->start_minor, this_span->end_minor, + this_span->cumulative_value, true ); + } + + // Truncate this_span to the current block. + this_span->start_major = std::max( this_span->start_major, trailing_edge_major ); + this_span->end_major = std::min( this_span->end_major, leading_edge_major ); + + // 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. + this_span->start_minor = new_start_minor; + this_span->cumulative_value = next_cumulative_transparency; + + new_start_minor = leading_edge_minor; + current_transparency = new_transparency; +} + templatecumulative_value, - current_transparency, distance ); - // 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 | - // +---+---+ <- leading edge major - // | B | C | - // +---+---+ <- 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 must exist. - // C is remainder of current row. This must exist. - // 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 and B are opaque and have no spans. - if( check( 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 ); - } - - // Emit the B span if present, placing it before the current span in the list - if( trailing_edge_minor > this_span->start_minor ) { - spans.emplace( this_span, - std::max( this_span->start_major, trailing_edge_major ), - std::min( this_span->end_major, leading_edge_major ), - this_span->start_minor, trailing_edge_minor, - next_cumulative_transparency ); - } - - // Overwrite new_start_minor since previous tile is transparent. - new_start_minor = trailing_edge_minor; - } - - // Emit the D span if present, placing it after the current span in the list - if( leading_edge_major < this_span->end_major ) { - // Pass true to the span constructor to set skip_first_row to true - // This prevents the same row we are currently checking being check by the - // new D span - spans.emplace( std::next( this_span ), - leading_edge_major, this_span->end_major, - this_span->start_minor, this_span->end_minor, - this_span->cumulative_value, true ); - } - - // Truncate this_span to the current block. - this_span->start_major = std::max( this_span->start_major, trailing_edge_major ); - this_span->end_major = std::min( this_span->end_major, leading_edge_major ); - - // 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. - this_span->start_minor = new_start_minor; - this_span->cumulative_value = next_cumulative_transparency; - - new_start_minor = leading_edge_minor; - current_transparency = new_transparency; + // 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 @@ -457,77 +475,11 @@ void cast_vertical_zlight_segment( continue; } - const T next_cumulative_transparency = accumulate( this_span->cumulative_value, - current_transparency, distance ); - // 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 | - // +---+---+ <- leading edge major - // | B | C | - // +---+---+ <- 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 must exist. - // C is remainder of current row. This must exist. - // 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 and B are opaque and have no spans. - if( check( 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 ); - } - - // Emit the B span if present, placing it before the current span in the list - if( trailing_edge_minor > this_span->start_minor ) { - spans.emplace( this_span, - std::max( this_span->start_major, trailing_edge_major ), - std::min( this_span->end_major, leading_edge_major ), - this_span->start_minor, trailing_edge_minor, - next_cumulative_transparency ); - } - - // Overwrite new_start_minor since previous tile is transparent. - new_start_minor = trailing_edge_minor; - } - - // Emit the D span if present, placing it after the current span in the list - if( leading_edge_major < this_span->end_major ) { - // Pass true to the span constructor to set skip_first_row to true - // This prevents the same row we are currently checking being check by the - // new D span - spans.emplace( std::next( this_span ), - leading_edge_major, this_span->end_major, - this_span->start_minor, this_span->end_minor, - this_span->cumulative_value, true ); - } - - // Truncate this_span to the current block. - this_span->start_major = std::max( this_span->start_major, trailing_edge_major ); - this_span->end_major = std::min( this_span->end_major, leading_edge_major ); - - // 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. - this_span->start_minor = new_start_minor; - this_span->cumulative_value = next_cumulative_transparency; - - new_start_minor = leading_edge_minor; - current_transparency = new_transparency; + // 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 From 49bb7b2cfca559475edf1463245e13db24e57f08 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 6 Jul 2019 07:18:02 +0200 Subject: [PATCH 10/17] Use int_least8_t for rise and run --- src/shadowcasting.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index 9a1adcd0d8fc3..346655e94d9dc 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -7,7 +7,7 @@ #include "line.h" struct slope { - slope( int rise, int run ) { + slope( int_least8_t rise, int_least8_t run ) { // Ensure run is always positive for the comparison operators this->run = abs( run ); if( run < 0 ) { @@ -17,8 +17,10 @@ struct slope { } } - int rise; - int run; + // 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 ) From 61de3ad0cde6fdbeb77375ad3a9843ce2c9f84b1 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 6 Jul 2019 16:34:43 +0200 Subject: [PATCH 11/17] Fix memory leak --- tests/shadowcasting_test.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/shadowcasting_test.cpp b/tests/shadowcasting_test.cpp index 9f50490e46ba2..c1075bb57bf3f 100644 --- a/tests/shadowcasting_test.cpp +++ b/tests/shadowcasting_test.cpp @@ -482,9 +482,8 @@ static void run_spot_check( const grid_overlay &test_case, const grid_overlay &e std::array transparency_cache; std::array floor_cache; - int z = fov_3d ? 0 : 11; const int upper_bound = fov_3d ? OVERMAP_LAYERS : 12; - for( ; z < upper_bound; ++z ) { + 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; @@ -529,8 +528,12 @@ static void run_spot_check( const grid_overlay &test_case, const grid_overlay &e trans_grid << '\n'; expected_grid << '\n'; actual_grid << '\n'; - delete caches[gz]; } + + 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() ); From 9e93eff2c77a5d99e92a0d8d5b4bdbe6ac57bb0b Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 6 Jul 2019 17:10:22 +0200 Subject: [PATCH 12/17] Cleanup template parameters --- src/shadowcasting.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index 346655e94d9dc..c6b6b31dad732 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -149,7 +149,7 @@ static void split_span( std::list> &spans, typename std::list>:: current_transparency = new_transparency; } -template @@ -194,7 +194,7 @@ void cast_horizontal_zlight_segment( 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 * zz; + 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; @@ -220,8 +220,8 @@ void cast_horizontal_zlight_segment( 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; + 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 // ---> @@ -525,42 +525,42 @@ void cast_zlight( // . // .. // @.. - cast_horizontal_zlight_segment < 0, 1, 0, 1, 0, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, 1, 1, 0, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // ... // .. // @ - cast_horizontal_zlight_segment < 1, 0, 0, 0, 1, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 1, 0, 0, 1, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @.. // .. // . - cast_horizontal_zlight_segment < 0, -1, 0, 1, 0, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, -1, 1, 0, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // ... // .. // @ - cast_horizontal_zlight_segment < -1, 0, 0, 0, 1, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < -1, 0, 0, 1, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // . // .. // ..@ - cast_horizontal_zlight_segment < 0, 1, 0, -1, 0, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, 1, -1, 0, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @ // .. // ... - cast_horizontal_zlight_segment < 1, 0, 0, 0, -1, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 1, 0, 0, -1, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // ..@ // .. // . - cast_horizontal_zlight_segment < 0, -1, 0, -1, 0, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, -1, -1, 0, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @ // .. // ... - cast_horizontal_zlight_segment < -1, 0, 0, 0, -1, 0, -1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < -1, 0, 0, -1, -1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // Up lateral @@ -568,42 +568,42 @@ void cast_zlight( // . // .. // @.. - cast_horizontal_zlight_segment < 0, 1, 0, 1, 0, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, 1, 1, 0, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // ... // .. // @ - cast_horizontal_zlight_segment < 1, 0, 0, 0, 1, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 1, 0, 0, 1, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @.. // .. // . - cast_horizontal_zlight_segment < 0, -1, 0, 1, 0, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, -1, 1, 0, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // ... // .. // @ - cast_horizontal_zlight_segment < -1, 0, 0, 0, 1, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < -1, 0, 0, 1, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // . // .. // ..@ - cast_horizontal_zlight_segment < 0, 1, 0, -1, 0, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, 1, -1, 0, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @ // .. // ... - cast_horizontal_zlight_segment < 1, 0, 0, 0, -1, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 1, 0, 0, -1, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // ..@ // .. // . - cast_horizontal_zlight_segment < 0, -1, 0, -1, 0, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < 0, -1, -1, 0, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @ // .. // ... - cast_horizontal_zlight_segment < -1, 0, 0, 0, -1, 0, 1, T, calc, check, accumulate > ( + cast_horizontal_zlight_segment < -1, 0, 0, -1, 1, T, calc, check, accumulate > ( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // Straight up From 1f2edfbbe4ad3832a750c1d542d5897d3ee85766 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 8 Jul 2019 11:45:54 +0200 Subject: [PATCH 13/17] Test 3d fov in all vision tests and remove the code that allows disabling it --- tests/vision_test.cpp | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) 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(); From 5b20d4551bc6d665f4d52ff8131051c777457928 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 8 Jul 2019 12:41:27 +0200 Subject: [PATCH 14/17] Rename check to is_transparent and fix comments It somehow took me until now to realize that y coordinates are counted from the "top" of the screen not the bottom, so fix the comment diagrams --- src/shadowcasting.cpp | 154 +++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 76 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index c6b6b31dad732..fe061236371df 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -8,7 +8,7 @@ struct slope { slope( int_least8_t rise, int_least8_t run ) { - // Ensure run is always positive for the comparison operators + // Ensure run is always positive for the inequality operators this->run = abs( run ); if( run < 0 ) { this->rise = -rise; @@ -42,7 +42,7 @@ inline bool operator>=( const slope &lhs, const slope &rhs ) } 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. + // 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 ) @@ -69,7 +69,7 @@ struct span { * Handle splitting the current span in cast_horizontal_zlight_segment and * cast_vertical_zlight_segment to avoid as much code duplication as possible */ -template +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, @@ -103,7 +103,7 @@ static void split_span( std::list> &spans, typename std::list>:: // 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 and B are opaque and have no spans. - if( check( current_transparency, last_intensity ) ) { + 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, @@ -151,7 +151,7 @@ static void split_span( std::list> &spans, typename std::list>:: template void cast_horizontal_zlight_segment( const std::array &output_caches, @@ -297,15 +297,16 @@ void cast_horizontal_zlight_segment( } // 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 ); + 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( !check( current_transparency, last_intensity ) ) { + if( !is_transparent( current_transparency, last_intensity ) ) { this_span->start_major = leading_edge_major; } } @@ -315,7 +316,7 @@ void cast_horizontal_zlight_segment( // 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( !check( current_transparency, last_intensity ) ) { + } 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 @@ -332,7 +333,7 @@ void cast_horizontal_zlight_segment( template void cast_vertical_zlight_segment( const std::array &output_caches, @@ -478,15 +479,16 @@ void cast_vertical_zlight_segment( } // 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 ); + 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( !check( current_transparency, last_intensity ) ) { + if( !is_transparent( current_transparency, last_intensity ) ) { this_span->start_major = leading_edge_major; } } @@ -496,7 +498,7 @@ void cast_vertical_zlight_segment( // 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( !check( current_transparency, last_intensity ) ) { + } 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 @@ -512,7 +514,7 @@ void cast_vertical_zlight_segment( } template void cast_zlight( const std::array &output_caches, @@ -522,126 +524,126 @@ void cast_zlight( { // 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, check, accumulate > ( + 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, check, accumulate > ( + 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, check, accumulate > ( + // ..@ + // .. + // . + 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, check, accumulate > ( + // .. + // ... + 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, check, accumulate > ( + 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, check, accumulate > ( + 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, check, accumulate > ( + 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, check, accumulate > ( + 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, check, accumulate > ( + 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, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - // @.. - // .. - // . - cast_horizontal_zlight_segment < 0, -1, 1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - // ... - // .. - // @ - cast_horizontal_zlight_segment < -1, 0, 0, 1, 1, T, calc, check, accumulate > ( + 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, check, accumulate > ( + 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, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - // ..@ - // .. - // . - cast_horizontal_zlight_segment < 0, -1, -1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); - // @ // .. - // ... - cast_horizontal_zlight_segment < -1, 0, 0, -1, 1, T, calc, check, accumulate > ( + // @ + 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, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @. // .. - cast_vertical_zlight_segment < 1, -1, 1, T, calc, check, accumulate > ( + 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, check, accumulate > ( + // @. + 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, check, accumulate > ( + 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, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // @. // .. - cast_vertical_zlight_segment < 1, -1, -1, T, calc, check, accumulate > ( + 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, check, accumulate > ( + // @. + 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, check, accumulate > ( + 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 ); } From 0ae920be3efbb81f8e00e2e6912c7c011850de28 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 8 Jul 2019 15:17:03 +0200 Subject: [PATCH 15/17] Fix erroneous cumulative transparency incrementation --- src/shadowcasting.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index fe061236371df..cc6c3d1d6cad3 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -128,7 +128,7 @@ static void split_span( std::list> &spans, typename std::list>:: // Emit the D span if present, placing it after the current span in the list if( leading_edge_major < this_span->end_major ) { // Pass true to the span constructor to set skip_first_row to true - // This prevents the same row we are currently checking being check by the + // This prevents the same row we are currently checking being checked by the // new D span spans.emplace( std::next( this_span ), leading_edge_major, this_span->end_major, @@ -143,7 +143,6 @@ static void split_span( std::list> &spans, typename std::list>:: // 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. this_span->start_minor = new_start_minor; - this_span->cumulative_value = next_cumulative_transparency; new_start_minor = leading_edge_minor; current_transparency = new_transparency; From 494a33bee1aa23576042dfad0b80fb0a40910ccd Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Thu, 11 Jul 2019 22:34:49 +0200 Subject: [PATCH 16/17] WIP rewrite of span splitting --- src/shadowcasting.cpp | 237 +++++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 105 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index cc6c3d1d6cad3..ebe0a0ca26140 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -69,85 +69,16 @@ struct span { * Handle splitting the current span in cast_horizontal_zlight_segment and * cast_vertical_zlight_segment to avoid as much code duplication as possible */ -template +/*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 ) { - const T next_cumulative_transparency = accumulate( this_span->cumulative_value, - current_transparency, distance ); - // 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 | - // +---+---+ <- leading edge major - // | B | C | - // +---+---+ <- 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 must exist. - // C is remainder of current row. This must exist. - // 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 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 ); - } - - // Emit the B span if present, placing it before the current span in the list - if( trailing_edge_minor > this_span->start_minor ) { - spans.emplace( this_span, - std::max( this_span->start_major, trailing_edge_major ), - std::min( this_span->end_major, leading_edge_major ), - this_span->start_minor, trailing_edge_minor, - next_cumulative_transparency ); - } - - // Overwrite new_start_minor since previous tile is transparent. - new_start_minor = trailing_edge_minor; - } - - // Emit the D span if present, placing it after the current span in the list - if( leading_edge_major < this_span->end_major ) { - // Pass true to the span constructor to set skip_first_row to true - // This prevents the same row we are currently checking being checked by the - // new D span - spans.emplace( std::next( this_span ), - leading_edge_major, this_span->end_major, - this_span->start_minor, this_span->end_minor, - this_span->cumulative_value, true ); - } - - // Truncate this_span to the current block. - this_span->start_major = std::max( this_span->start_major, trailing_edge_major ); - this_span->end_major = std::min( this_span->end_major, leading_edge_major ); - // 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. - this_span->start_minor = new_start_minor; - - new_start_minor = leading_edge_minor; - current_transparency = new_transparency; } - +*/ template previous_row_transparencies; + std::bitset<128> current_row_transparencies; + // 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 ); @@ -216,8 +149,10 @@ void cast_horizontal_zlight_segment( break; } - bool started_span = false; const int z_index = current.z + OVERMAP_DEPTH; + + slope 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; @@ -275,39 +210,129 @@ void cast_horizontal_zlight_segment( 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( !started_span ) { - // Need to reset minor slope, because we're starting a new line - new_start_minor = leading_edge_minor; - started_span = true; - } - - if( new_transparency == current_transparency ) { + 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. - 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 ); + // 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_major; + } + } 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_major; + } + } 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 - // 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( this_span->start_major >= trailing_edge_major ) { + this_span->start_minor = current_start_minor; + } + + previous_row_transparencies = std::move( current_row_transparencies ); + current_row_transparencies.reset(); + + if( started_block ) { + first_row = false; + } } if( !started_block ) { @@ -329,7 +354,7 @@ void cast_horizontal_zlight_segment( } } } - +/* template @@ -533,24 +558,25 @@ void cast_zlight( // ... 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 ); // . @@ -608,7 +634,7 @@ void cast_zlight( output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); // Straight up - + /* // @. // .. cast_vertical_zlight_segment < 1, 1, 1, T, calc, is_transparent, accumulate > ( @@ -644,6 +670,7 @@ void cast_zlight( // .@ 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 From 9236a76f232d2306b7cf160f929fe015e1f36cd5 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sun, 14 Jul 2019 17:58:58 +0200 Subject: [PATCH 17/17] Fix some of the bugs --- src/shadowcasting.cpp | 44 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/shadowcasting.cpp b/src/shadowcasting.cpp index ebe0a0ca26140..a46b32a02385e 100644 --- a/src/shadowcasting.cpp +++ b/src/shadowcasting.cpp @@ -122,6 +122,8 @@ void cast_horizontal_zlight_segment( 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 ); @@ -151,7 +153,7 @@ void cast_horizontal_zlight_segment( const int z_index = current.z + OVERMAP_DEPTH; - slope current_start_minor = this_span->start_minor; + 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; @@ -276,7 +278,7 @@ void cast_horizontal_zlight_segment( this_span->start_minor, trailing_edge_minor, next_cumulative_transparency ); - current_start_minor = trailing_edge_major; + current_start_minor = trailing_edge_minor; } } else if( trailing_edge_minor > current_start_minor ) { // A span not present, so previous row might be opaque @@ -303,7 +305,7 @@ void cast_horizontal_zlight_segment( next_cumulative_transparency, true ); } - current_start_minor = trailing_edge_major; + current_start_minor = trailing_edge_minor; } } else { // Leading edge of the previous square since opaque, @@ -320,11 +322,39 @@ void cast_horizontal_zlight_segment( // 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 ) ) { - this_span->start_major = leading_edge_major; - } + // 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 ); + } - if( this_span->start_major >= trailing_edge_major ) { - this_span->start_minor = current_start_minor; + 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 );