Skip to content

Commit

Permalink
Base sunrise and sunset on astronomical maths
Browse files Browse the repository at this point in the history
This is the first change that allows the sun angle calculations to
actually influence the game.

We switch the sunrise and sunset times to match the sun positions as
calculated by the astronomical equations surrounding the location of the
sun.

This requires changing a few other places in the code, and updating a
lot of tests.

Heavinly inspired by Hirmuolio's work in #47570, but reimplemented
rather than cherry-picked.

Co-authored-by: Hirmuolio <[email protected]>
  • Loading branch information
jbytheway and Hirmuolio committed May 17, 2021
1 parent 29f9ba2 commit f99005f
Show file tree
Hide file tree
Showing 10 changed files with 526 additions and 482 deletions.
455 changes: 237 additions & 218 deletions src/calendar.cpp

Large diffs are not rendered by default.

23 changes: 15 additions & 8 deletions src/calendar.h
Original file line number Diff line number Diff line change
Expand Up @@ -574,18 +574,23 @@ bool is_day( const time_point &p );
bool is_dusk( const time_point &p );
/** Returns true if it's currently dawn - between sunrise and twilight_duration after sunrise. */
bool is_dawn( const time_point &p );
/** Returns the current seasonally-adjusted maximum daylight level */
double current_daylight_level( const time_point &p );
/** How much light is provided in full daylight */
double default_daylight_level();
/** Returns the current sunlight or moonlight level through the preceding functions.
* By default, returns sunlight level for vision, with moonlight providing a measurable amount
* of light. with vision == false, returns sunlight for solar panel purposes, and moonlight
* provides 0 light */
float sunlight( const time_point &p, bool vision = true );
/** Returns the current sunlight.
* Based entirely on astronomical circumstances; does not account for e.g.
* weather.
* For most situations you actually want to call the below function which also
* includes moonlight. */
float sun_light_at( const time_point &p );
/** Returns the current sunlight plus moonlight level.
* Based entirely on astronomical circumstances; does not account for e.g.
* weather. */
float sun_moon_light_at( const time_point &p );
/** How much light is provided at the solar noon nearest to given time */
double sun_moon_light_at_noon_near( const time_point &p );

std::pair<units::angle, units::angle> sun_azimuth_altitude(
time_point, lat_long, float timezone );
time_point, lat_long, time_duration timezone );

/** Returns the offset by which a ray of sunlight would move when shifting down
* one z-level, or nullopt if the sun is below the horizon.
Expand All @@ -595,6 +600,8 @@ std::pair<units::angle, units::angle> sun_azimuth_altitude(
cata::optional<rl_vec2d> sunlight_angle( const time_point &, lat_long );
cata::optional<rl_vec2d> sunlight_angle( const time_point & );

constexpr time_duration timezone_boston = -5_hours;

enum class weekdays : int {
SUNDAY = 0,
MONDAY,
Expand Down
9 changes: 6 additions & 3 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11947,14 +11947,17 @@ bool Character::sees_with_infrared( const Creature &critter ) const
}

map &here = get_map();
// Use range based on default daylight, not actual current light, since
// we're seeing in infra red not via light.
int range = sight_range( default_daylight_level() );

if( is_player() || critter.is_player() ) {
// Players should not use map::sees
// Likewise, players should not be "looked at" with map::sees, not to break symmetry
return here.pl_line_of_sight( critter.pos(),
sight_range( current_daylight_level( calendar::turn ) ) );
return here.pl_line_of_sight( critter.pos(), range );
}

return here.sees( pos(), critter.pos(), sight_range( current_daylight_level( calendar::turn ) ) );
return here.sees( pos(), critter.pos(), range );
}

bool Character::is_visible_in_range( const Creature &critter, const int range ) const
Expand Down
4 changes: 2 additions & 2 deletions src/editmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,9 +744,9 @@ void editmap::update_view_with_help( const std::string &txt, const std::string &
const std::string u_see_msg = player_character.sees( target ) ? _( "yes" ) : _( "no" );
mvwprintw( w_info, point( 1, off++ ), _( "dist: %d u_see: %s veh: %s scent: %d" ),
rl_dist( player_character.pos(), target ), u_see_msg, veh_msg, get_scent().get( target ) );
mvwprintw( w_info, point( 1, off++ ), _( "sight_range: %d, daylight_sight_range: %d," ),
mvwprintw( w_info, point( 1, off++ ), _( "sight_range: %d, noon_sight_range: %d," ),
player_character.sight_range( g->light_level( player_character.posz() ) ),
player_character.sight_range( current_daylight_level( calendar::turn ) ) );
player_character.sight_range( sun_moon_light_at_noon_near( calendar::turn ) ) );
mvwprintw( w_info, point( 1, off++ ), _( "cache{transp:%.4f seen:%.4f cam:%.4f}" ),
map_cache.transparency_cache[target.x][target.y],
map_cache.seen_cache[target.x][target.y],
Expand Down
7 changes: 4 additions & 3 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4023,7 +4023,7 @@ float game::natural_light_level( const int zlev ) const

// Sunlight/moonlight related stuff
if( !weather.lightning_active ) {
ret = sunlight( calendar::turn );
ret = sun_moon_light_at( calendar::turn );
} else {
// Recent lightning strike has lit the area
ret = default_daylight_level();
Expand Down Expand Up @@ -7646,8 +7646,9 @@ void game::reset_item_list_state( const catacurses::window &window, int height,

void game::list_items_monsters()
{
std::vector<Creature *> mons = u.get_visible_creatures( current_daylight_level( calendar::turn ) );
// whole reality bubble
// Search whole reality bubble because each function internally verifies
// the visibilty of the items / monsters in question.
std::vector<Creature *> mons = u.get_visible_creatures( 60 );
const std::vector<map_item_stack> items = find_nearby_items( 60 );

if( mons.empty() && items.empty() ) {
Expand Down
5 changes: 5 additions & 0 deletions src/units.h
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,11 @@ inline units::angle asin( double x )
return from_radians( std::asin( x ) );
}

inline units::angle acos( double x )
{
return from_radians( std::acos( x ) );
}

static const std::vector<std::pair<std::string, energy>> energy_units = { {
{ "mJ", 1_mJ },
{ "J", 1_J },
Expand Down
2 changes: 1 addition & 1 deletion src/weather.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ void glare( const weather_type_id &w )

int incident_sunlight( const weather_type_id &wtype, const time_point &t )
{
return std::max<float>( 0.0f, sunlight( t, false ) + wtype->light_modifier );
return std::max<float>( 0.0f, sun_light_at( t ) + wtype->light_modifier );
}

static inline void proc_weather_sum( const weather_type_id &wtype, weather_sum &data,
Expand Down
8 changes: 4 additions & 4 deletions tests/char_sight_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ TEST_CASE( "light and fine_detail_vision_mod", "[character][sight][light][vision
// Build map cache including lightmap
here.build_map_cache( 0, false );
REQUIRE( g->is_in_sunlight( dummy.pos() ) );
// ambient_light_at is 100.0 in full sun (this fails if lightmap cache is not built)
REQUIRE( here.ambient_light_at( dummy.pos() ) == Approx( 100.0f ) );
// ambient_light_at is ~100.0 in full sun (this fails if lightmap cache is not built)
REQUIRE( here.ambient_light_at( dummy.pos() ) == Approx( 100.0f ).margin( 10 ) );

// 1.0 is LIGHT_AMBIENT_LIT or brighter
CHECK( dummy.fine_detail_vision_mod() == Approx( 1.0f ) );
Expand Down Expand Up @@ -263,7 +263,7 @@ TEST_CASE( "ursine vision", "[character][ursine][vision]" )
here.build_map_cache( 0, false );
light_here = here.ambient_light_at( dummy.pos() );
REQUIRE( g->is_in_sunlight( dummy.pos() ) );
REQUIRE( light_here == Approx( 100.0f ) );
REQUIRE( light_here == Approx( 100.0f ).margin( 10 ) );

THEN( "impaired sight, with 4 tiles of range" ) {
dummy.recalc_sight_limits();
Expand All @@ -281,7 +281,7 @@ TEST_CASE( "ursine vision", "[character][ursine][vision]" )
dummy.recalc_sight_limits();
CHECK_FALSE( dummy.sight_impaired() );
CHECK( dummy.unimpaired_range() == 60 );
CHECK( dummy.sight_range( light_here ) == 87 );
CHECK( dummy.sight_range( light_here ) == 88 );
}
}
}
Expand Down
57 changes: 19 additions & 38 deletions tests/moon_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,30 +216,20 @@ TEST_CASE( "moonlight at dawn and dusk", "[calendar][moon][moonlight][dawn][dusk
time_point new_sunset = sunset( new_midnight );
time_point new_noon = new_midnight + 12_hours;

// Daylight level should be 100 at first new moon
float daylight_level = current_daylight_level( new_noon );
float half_twilight = ( daylight_level + 1.0f ) / 2.0f;
// Daylight level should be ~100 at first new moon
float daylight_level = sun_moon_light_at( new_noon );
CHECK( daylight_level == Approx( 100 ).margin( 10 ) );
float moonlight_level = 1.0f;

THEN( "at night, light is only moonlight" ) {
CHECK( sunlight( new_sunset + 61_minutes ) == moonlight_level );
CHECK( sunlight( new_midnight ) == moonlight_level );
CHECK( sunlight( new_sunrise - 1_minutes ) == moonlight_level );
CHECK( sun_moon_light_at( new_sunset + 2_hours ) == moonlight_level );
CHECK( sun_moon_light_at( new_midnight ) == moonlight_level );
CHECK( sun_moon_light_at( new_sunrise - 2_hours ) == moonlight_level );
}
THEN( "at dawn, light increases from moonlight to daylight" ) {
CHECK( sunlight( new_sunrise ) == moonlight_level );
CHECK( sunlight( new_sunrise + 30_minutes ) == Approx( half_twilight ) );
CHECK( sunlight( new_sunrise + 1_hours ) == daylight_level );
}
THEN( "after dawn, until dusk, light is full daylight" ) {
CHECK( sunlight( new_sunrise + 61_minutes ) == daylight_level );
CHECK( sunlight( new_noon ) == daylight_level );
CHECK( sunlight( new_sunset - 1_minutes ) == daylight_level );
}
THEN( "at dusk, light decreases from daylight to moonlight" ) {
CHECK( sunlight( new_sunset ) == daylight_level );
CHECK( sunlight( new_sunset + 30_minutes ) == Approx( half_twilight ) );
CHECK( sunlight( new_sunset + 1_hours ) == moonlight_level );
CHECK( sun_moon_light_at( new_sunrise ) > sun_moon_light_at( new_sunrise - 2_hours ) );
CHECK( sun_moon_light_at( new_sunrise + 1_hours ) ==
sun_light_at( new_sunrise + 1_hours ) + moonlight_level );
}
}

Expand All @@ -252,29 +242,20 @@ TEST_CASE( "moonlight at dawn and dusk", "[calendar][moon][moonlight][dawn][dusk
time_point full_noon = full_midnight + 12_hours;

// Daylight level is higher, later in the season (~104 at first full moon)
float daylight_level = current_daylight_level( full_noon );
float half_twilight = ( daylight_level + 10.0f ) / 2.0f;
float daylight_level = sun_moon_light_at( full_noon );
CHECK( daylight_level == Approx( 120 ).margin( 10 ) );
float moonlight_level = 10.0f;

THEN( "at night, light is only moonlight" ) {
CHECK( sunlight( full_sunset + 61_minutes ) == moonlight_level );
CHECK( sunlight( full_midnight ) == moonlight_level );
CHECK( sunlight( full_sunrise - 1_minutes ) == moonlight_level );
CHECK( sun_moon_light_at( full_sunset + 2_hours ) == moonlight_level );
CHECK( sun_moon_light_at( full_midnight ) == moonlight_level );
CHECK( sun_moon_light_at( full_sunrise - 2_hours ) == moonlight_level );
}
THEN( "at dawn, light increases from moonlight to daylight" ) {
CHECK( sunlight( full_sunrise ) == moonlight_level );
CHECK( sunlight( full_sunrise + 30_minutes ) == Approx( half_twilight ) );
CHECK( sunlight( full_sunrise + 1_hours ) == daylight_level );
}
THEN( "after dawn, until dusk, light is full daylight" ) {
CHECK( sunlight( full_sunrise + 61_minutes ) == daylight_level );
CHECK( sunlight( full_noon ) == daylight_level );
CHECK( sunlight( full_sunset - 1_minutes ) == daylight_level );
}
THEN( "at dusk, light decreases from daylight to moonlight" ) {
CHECK( sunlight( full_sunset ) == daylight_level );
CHECK( sunlight( full_sunset + 30_minutes ) == Approx( half_twilight ) );
CHECK( sunlight( full_sunset + 1_hours ) == moonlight_level );
CHECK( sun_moon_light_at( full_sunrise ) >
sun_moon_light_at( full_sunrise - 2_hours ) );
CHECK( sun_moon_light_at( full_sunrise + 1_hours ) ==
sun_light_at( full_sunrise + 1_hours ) + moonlight_level );
}
}
}
Expand Down Expand Up @@ -314,7 +295,7 @@ static float phase_moonlight( const float phase_scale, const moon_phase expect_p
expect_phase_enum ) );

// Finally, get the amount of moonlight
return sunlight( this_night );
return sun_moon_light_at( this_night );
}

// Moonlight level varies with moon phase, from 1.0 at new moon to 10.0 at full moon.
Expand Down
Loading

0 comments on commit f99005f

Please sign in to comment.