OverviewFilesCommitsIssuesDocsReleasesPeers

godot-witchy_world_demo / addons / jc.godot.time-of-day / Code / TimeOfDay / TimeOfDay.gd

519 lines · gd ref:
tool
class_name TimeOfDay extends Node
"""========================================================
°                         TimeOfDay.
°                   ======================
°
°   Category: TimeOfDay
°   -----------------------------------------------------
°   Description:
°       Time of Day manager.
°   -----------------------------------------------------
°   Copyright:
°               J. Cuellar 2021. MIT License.
°                   See: LICENSE File.
========================================================"""

# Target
#----------------------------------------------------------
var __dome: Skydome = null
var __dome_found: bool = false

var dome_path: NodePath setget set_dome_path
func set_dome_path(value: NodePath) -> void:
	dome_path = value
	if value != null:
		__dome = get_node_or_null(value) as Skydome
	
	__dome_found = false if __dome == null else true
	__set_celestial_coords()

# DateTime
#----------------------------------------------------------
var system_sync: bool = false
var total_cycle_in_minutes: float = 15.0

var total_hours: float = 7.0 setget set_total_hours
func set_total_hours(value: float) -> void:
	total_hours = value
	emit_signal("total_hours_changed", value)
	if Engine.editor_hint:
		__set_celestial_coords()

var day: int = 12 setget set_day
func set_day(value: int) -> void:
	day = value 
	emit_signal("day_changed", value)
	if Engine.editor_hint:
		__set_celestial_coords()

var month: int = 2 setget set_month
func set_month(value: int) -> void:
	month = value 
	emit_signal("month_changed", value)
	if Engine.editor_hint:
		__set_celestial_coords()

var year: int = 2021 setget set_year
func set_year(value: int) -> void:
	year = value 
	emit_signal("year_changed", value)
	if Engine.editor_hint:
		__set_celestial_coords()

func is_learp_year() -> bool:
	return DateTimeUtil.compute_leap_year(year)

func max_days_per_month() -> int:
	match month:
		1, 3, 5, 7, 8, 10, 12:
			return 31
		2:
			return 29 if is_learp_year() else 28
	
	return 30

func time_cycle_duration() -> float:
	return total_cycle_in_minutes * 60.0

func is_begin_of_time() -> bool:
	return year == 1 && month == 1 && day == 1

func is_end_of_time() -> bool:
	return year == 9999 && month == 12 && day == 31

var date_time_os: Dictionary
signal total_hours_changed(value)
signal day_changed(value)
signal month_changed(value)
signal year_changed(value)

# Planetary
#----------------------------------------------------------
enum CelestialCalculationsMode{
	Simple = 0,
	Realistic
}

var celestials_calculations: int = 0 setget set_celestials_calculations
func set_celestials_calculations(value: int) -> void:
	celestials_calculations = value
	if Engine.editor_hint:
		__set_celestial_coords()
	
	property_list_changed_notify()

var latitude: float = 0.0 setget set_latitude
func set_latitude(value: float) -> void:
	latitude = value
	if Engine.editor_hint:
		__set_celestial_coords()

var longitude: float = 0.0 setget set_longitude
func set_longitude(value: float) -> void:
	longitude = value
	if Engine.editor_hint:
		__set_celestial_coords()

var utc: float = 0.0 setget set_utc
func set_utc(value: float) -> void:
	utc = value
	if Engine.editor_hint:
		__set_celestial_coords()

var celestials_update_time: float = 0.0
var __celestials_update_timer: float = 0.0

var compute_moon_coords: bool = false setget set_compute_moon_coords
func set_compute_moon_coords(value: bool) -> void:
	compute_moon_coords = value
	if Engine.editor_hint:
		__set_celestial_coords()
	
	property_list_changed_notify()

var compute_deep_space_coords: bool = false setget set_compute_deep_space_coords
func set_compute_deep_space_coords(value: bool) -> void:
	compute_deep_space_coords = value
	if Engine.editor_hint:
		__set_celestial_coords()

var moon_coords_offset := Vector2(0.0, 0.0) setget set_moon_coords_offset
func set_moon_coords_offset(value: Vector2) -> void:
	moon_coords_offset = value
	if Engine.editor_hint:
		__set_celestial_coords()

func __get_latitude_rad() -> float:
	return latitude * TOD_Math.DEG_TO_RAD

func __get_total_hours_utc() -> float:
	return total_hours - utc

func __get_time_scale() -> float:
	return (367.0 * year - (7.0 * (year + ((month + 9.0) / 12.0))) / 4.0 +\
		(275.0 * month) / 9.0 + day - 730530.0) + total_hours / 24.0

func __get_oblecl() -> float:
	return (23.4393 - 2.563e-7 * __get_time_scale()) * TOD_Math.DEG_TO_RAD

var __sun_coords:= Vector2.ZERO
var __moon_coords:= Vector2.ZERO
var __sun_distance: float
var __true_sun_longitude: float 
var __mean_sun_longitude: float
var __sideral_time: float
var __local_sideral_time: float

var __sun_orbital_elements:= OrbitalElements.new()
var __moon_orbital_elements:= OrbitalElements.new()


# Override
#----------------------------------------------------------
func _init() -> void:
	set_total_hours(total_hours)
	set_day(day)
	set_month(month)
	set_year(year)
	set_latitude(latitude)
	set_longitude(longitude)
	set_utc(utc)

func _ready() -> void:
	set_dome_path(dome_path)

func _process(delta) -> void:
	if Engine.editor_hint:
		return
	
	if not system_sync:
		__time_process(delta)
		__repeat_full_cycle()
		__check_cycle()
	else:
		__get_date_time_os()
	
	__celestials_update_timer += delta;
	if __celestials_update_timer > celestials_update_time:
		__set_celestial_coords()
		__celestials_update_timer = 0.0

# DateTime
#----------------------------------------------------------
func set_time(hour: int, minute: int, second: int) -> void:
	set_total_hours(DateTimeUtil.hours_to_total_hours(hour, minute, second))

func __time_process(delta: float) -> void:
	if time_cycle_duration() != 0.0:
		set_total_hours(total_hours + delta / time_cycle_duration() * DateTimeUtil.TOTAL_HOURS)

func __get_date_time_os() -> void:
	date_time_os = OS.get_datetime()
	set_time(date_time_os.hour, date_time_os.minute, date_time_os.second)
	set_day(date_time_os.day)
	set_month(date_time_os.month)
	set_year(date_time_os.year)

func __repeat_full_cycle() -> void:
	if is_end_of_time() && total_hours >= 23.9999:
		set_year(1); set_month(1); set_day(1)
		set_total_hours(0.0)
		
	if is_begin_of_time() && total_hours < 0.0:
		set_year(9999); set_month(12); set_day(31)
		set_total_hours(23.9999)

func __check_cycle() -> void:
	if total_hours > 23.9999:
		set_day(day + 1)
		set_total_hours(0.0)
	if total_hours < 0.0000:
		set_day(day - 1)
		set_total_hours(23.9999)
	
	if day > max_days_per_month():
		set_month(month + 1)
		set_day(1)
	
	if day < 1:
		set_month(month - 1)
		set_day(31)
	
	if month > 12:
		set_year(year + 1)
		set_month(1)
	
	if month < 1:
		set_year(year - 1)
		set_month(12)

# Planetary
#----------------------------------------------------------
func __set_celestial_coords() -> void:
	if not __dome_found:
		return

	match celestials_calculations:
		CelestialCalculationsMode.Simple:
			__compute_simple_sun_coords()
			__dome.sun_altitude = __sun_coords.y
			__dome.sun_azimuth = __sun_coords.x
			if compute_moon_coords:
				__compute_simple_moon_coords()
				__dome.moon_altitude = __moon_coords.y
				__dome.moon_azimuth = __moon_coords.x
			
			if compute_deep_space_coords:
				var x = Quat(Vector3( (90 + latitude) * TOD_Math.DEG_TO_RAD, 0.0, 0.0))
				var y = Quat(Vector3(0.0, 0.0, __sun_coords.y * TOD_Math.DEG_TO_RAD))
				__dome.deep_space_quat = x * y
		
		CelestialCalculationsMode.Realistic:
			__compute_realistic_sun_coords()
			__dome.sun_altitude = __sun_coords.y * TOD_Math.RAD_TO_DEG
			__dome.sun_azimuth = __sun_coords.x * TOD_Math.RAD_TO_DEG
			if compute_moon_coords:
				__compute_realistic_moon_coords()
				__dome.moon_altitude = __moon_coords.y * TOD_Math.RAD_TO_DEG
				__dome.moon_azimuth = __moon_coords.x * TOD_Math.RAD_TO_DEG
			
			if compute_deep_space_coords:
				var x = Quat(Vector3( (90 + latitude) * TOD_Math.DEG_TO_RAD, 0.0, 0.0) )
				var y = Quat(Vector3(0.0, 0.0,  (180.0 - __local_sideral_time * TOD_Math.RAD_TO_DEG) * TOD_Math.DEG_TO_RAD)) 
				__dome.deep_space_quat = x * y

func __compute_simple_sun_coords() -> void:
	var altitude = (__get_total_hours_utc() + (TOD_Math.DEG_TO_RAD * longitude)) * (360/24)
	__sun_coords.y = (180.0 - altitude)
	__sun_coords.x = latitude

func __compute_simple_moon_coords() -> void:
	__moon_coords.y = (180.0 - __sun_coords.y) + moon_coords_offset.y
	__moon_coords.x = (180.0 + __sun_coords.x) + moon_coords_offset.x


func __compute_realistic_sun_coords() -> void:
	## Orbital Elements.
	__sun_orbital_elements.get_orbital_elements(0, __get_time_scale())
	__sun_orbital_elements.M = TOD_Math.rev(__sun_orbital_elements.M)
	
	# Mean anomaly in radians.
	var MRad: float = TOD_Math.DEG_TO_RAD * __sun_orbital_elements.M
	
	## Eccentric Anomaly
	var E: float = __sun_orbital_elements.M + TOD_Math.RAD_TO_DEG * __sun_orbital_elements.e *\
		sin(MRad) * (1 + __sun_orbital_elements.e * cos(MRad))
	
	var ERad: float = E * TOD_Math.DEG_TO_RAD
	
	## Rectangular coordinates.
	# Rectangular coordinates of the sun in the plane of the ecliptic.
	var xv: float = cos(ERad) - __sun_orbital_elements.e
	var yv: float = sin(ERad) * sqrt(1 - __sun_orbital_elements.e * __sun_orbital_elements.e)
	
	## Distance and true anomaly.
	# Convert to distance and true anomaly(r = radians, v = degrees).
	var r: float = sqrt(xv * xv + yv * yv)
	var v: float = TOD_Math.RAD_TO_DEG * atan2(yv, xv)
	__sun_distance = r
	
	## True longitude.
	var lonSun: float = v + __sun_orbital_elements.w
	lonSun = TOD_Math.rev(lonSun)
	
	var lonSunRad = TOD_Math.DEG_TO_RAD * lonSun
	__true_sun_longitude = lonSunRad
	
	## Ecliptic and ecuatorial coords.
	
	# Ecliptic rectangular coords.
	var xs: float = r * cos(lonSunRad)
	var ys: float = r * sin(lonSunRad)
	
	# Ecliptic rectangular coordinates rotate these to equatorial coordinates
	var obleclCos: float = cos(__get_oblecl())
	var obleclSin: float = sin(__get_oblecl())
	
	var xe: float = xs 
	var ye: float = ys * obleclCos - 0.0 * obleclSin
	var ze: float = ys * obleclSin + 0.0 * obleclCos
	
	## Ascencion and declination.
	var RA: float = TOD_Math.RAD_TO_DEG * atan2(ye, xe) / 15 # right ascension.
	var decl: float = atan2(ze, sqrt(xe * xe + ye * ye)) # declination
	
	# Mean longitude.
	var L: float = __sun_orbital_elements.w + __sun_orbital_elements.M
	L = TOD_Math.rev(L)
	
	__mean_sun_longitude = L
	
	## Sideral time and hour angle.
	var GMST0: float = ((L/15) + 12)
	__sideral_time = GMST0 + __get_total_hours_utc() + longitude / 15 # +15/15
	__local_sideral_time = TOD_Math.DEG_TO_RAD * __sideral_time * 15
	
	var HA: float = (__sideral_time - RA) * 15
	var HARAD: float = TOD_Math.DEG_TO_RAD * HA
	
	## Hour angle and declination in rectangular coords
	# HA and Decl in rectangular coords.
	var declCos: float = cos(decl)
	var x = cos(HARAD) * declCos # X Axis points to the celestial equator in the south.
	var y = sin(HARAD) * declCos # Y axis points to the horizon in the west.
	var z = sin(decl) # Z axis points to the north celestial pole.
	
	# Rotate the rectangualar coordinates system along of the Y axis.
	var sinLat: float = sin(latitude * TOD_Math.DEG_TO_RAD)
	var cosLat: float = cos(latitude * TOD_Math.DEG_TO_RAD)
	var xhor: float = x * sinLat - z * cosLat
	var yhor: float = y 
	var zhor: float = x * cosLat + z * sinLat
	
	## Azimuth and altitude.
	__sun_coords.x = atan2(yhor, xhor) + PI
	__sun_coords.y = (PI * 0.5) - asin(zhor) # atan2(zhor, sqrt(xhor * xhor + yhor * yhor))
	

func __compute_realistic_moon_coords() -> void:
	## Orbital Elements.
	__moon_orbital_elements.get_orbital_elements(1, __get_time_scale())
	__moon_orbital_elements.N = TOD_Math.rev(__moon_orbital_elements.N)
	__moon_orbital_elements.w = TOD_Math.rev(__moon_orbital_elements.w)
	__moon_orbital_elements.M = TOD_Math.rev(__moon_orbital_elements.M)
	
	var NRad: float = TOD_Math.DEG_TO_RAD * __moon_orbital_elements.N
	var IRad: float = TOD_Math.DEG_TO_RAD * __moon_orbital_elements.i
	var MRad: float = TOD_Math.DEG_TO_RAD * __moon_orbital_elements.M
	
	## Eccentric anomaly.
	var E: float = __moon_orbital_elements.M + TOD_Math.RAD_TO_DEG * __moon_orbital_elements.e * sin(MRad) *\
		(1 + __sun_orbital_elements.e * cos(MRad))
	
	var ERad = TOD_Math.DEG_TO_RAD * E
	
	## Rectangular coords and true anomaly
	# Rectangular coordinates of the sun in the plane of the ecliptic
	var xv: float = __moon_orbital_elements.a * (cos(ERad) - __moon_orbital_elements.e)
	var yv: float = __moon_orbital_elements.a * (sin(ERad) * sqrt(1 - __moon_orbital_elements.e * \
		__moon_orbital_elements.e)) * sin(ERad)
		
	# Convert to distance and true anomaly(r = radians, v = degrees)
	var r: float = sqrt(xv * xv + yv * yv)
	var v: float = TOD_Math.RAD_TO_DEG * atan2(yv, xv)
	v = TOD_Math.rev(v)
	
	var l: float = TOD_Math.DEG_TO_RAD * v + __moon_orbital_elements.w
	
	var cosL: float = cos(l)
	var sinL: float = sin(l)
	var cosNRad: float = cos(NRad)
	var sinNRad: float = sin(NRad)
	var cosIRad: float = cos(IRad)
	
	var xeclip: float = r * (cosNRad * cosL - sinNRad * sinL * cosIRad)
	var yeclip: float = r * (sinNRad * cosL + cosNRad * sinL * cosIRad)
	var zeclip: float = r * (sinL * sin(IRad))
	
	## Geocentric coords.
	# Geocentric position for the moon and Heliocentric position for the planets
	var lonecl: float = TOD_Math.RAD_TO_DEG * atan2(yeclip, xeclip)
	lonecl = TOD_Math.rev(lonecl)
	
	var latecl: float = TOD_Math.RAD_TO_DEG * atan2(zeclip, sqrt(xeclip * xeclip + yeclip * yeclip))
	
	# Get true sun longitude.
	var lonsun: float = __true_sun_longitude
	
	# Ecliptic longitude and latitude in radians
	var loneclRad: float = TOD_Math.DEG_TO_RAD * lonecl
	var lateclRad: float = TOD_Math.DEG_TO_RAD * latecl
	
	var nr: float = 1.0
	var xh: float = nr * cos(loneclRad) * cos(lateclRad)
	var yh: float = nr * sin(loneclRad) * cos(lateclRad)
	var zh: float = nr * sin(lateclRad)
	
	# Geocentric coords.
	var xs: float = 0.0
	var ys: float = 0.0
	
	# Convert the geocentric position to heliocentric position.
	var xg: float = xh + xs
	var yg: float = yh + ys
	var zg: float = zh
	
	## Ecuatorial coords.
	# Cobert xg, yg un equatorial coords.
	var obleclCos: float = cos(__get_oblecl())
	var obleclSin: float = sin(__get_oblecl())
	
	var xe: float = xg 
	var ye: float = yg * obleclCos - zg * obleclSin
	var ze: float = yg * obleclSin + zg * obleclCos
	
	# Right ascention.
	var RA: float = TOD_Math.RAD_TO_DEG * atan2(ye, xe)
	RA = TOD_Math.rev(RA)
	
	# Declination.
	var decl: float = TOD_Math.RAD_TO_DEG * atan2(ze, sqrt(xe * xe + ye * ye))
	var declRad: float = TOD_Math.DEG_TO_RAD * decl
	
	## Sideral time and hour angle.
	# Hour angle.
	var HA: float = ((__sideral_time * 15) - RA)
	HA = TOD_Math.rev(HA)
	var HARAD: float = TOD_Math.DEG_TO_RAD * HA
	
	# HA y Decl in rectangular coordinates.
	var declCos: float = cos(declRad)
	var xr: float = cos(HARAD) * declCos
	var yr: float = sin(HARAD) * declCos
	var zr: float = sin(declRad)
	
	# Rotate the rectangualar coordinates system along of the Y axis(radians).
	var sinLat: float = sin(__get_latitude_rad())
	var cosLat: float = cos(__get_latitude_rad())
	
	var xhor: float = xr * sinLat - zr * cosLat
	var yhor: float = yr 
	var zhor: float = xr * cosLat + zr * sinLat
	
	## Azimuth and altitude
	__moon_coords.x = atan2(yhor, xhor) + PI
	__moon_coords.y = (PI *0.5) - atan2(zhor, sqrt(xhor * xhor + yhor * yhor)) # Mathf.Asin(zhor)


func _get_property_list() -> Array:
	var ret: Array 
	ret.push_back({name = "Time Of Day", type=TYPE_NIL, usage=PROPERTY_USAGE_CATEGORY})
	
	ret.push_back({name = "Target", type=TYPE_NIL, usage=PROPERTY_USAGE_GROUP})
	ret.push_back({name = "dome_path", type=TYPE_NODE_PATH})
	
	ret.push_back({name = "DateTime", type=TYPE_NIL, usage=PROPERTY_USAGE_GROUP})
	ret.push_back({name = "system_sync", type=TYPE_BOOL})
		
	ret.push_back({name = "total_cycle_in_minutes", type=TYPE_REAL})
	ret.push_back({name = "total_hours", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="0.0, 24.0"})
	ret.push_back({name = "day", type=TYPE_INT, hint=PROPERTY_HINT_RANGE, hint_string="0, 31"})
	ret.push_back({name = "month", type=TYPE_INT, hint=PROPERTY_HINT_RANGE, hint_string="0, 12"})
	ret.push_back({name = "year", type=TYPE_INT, hint=PROPERTY_HINT_RANGE, hint_string="-9999, 9999"})

	ret.push_back({name = "Planetary And Location", type=TYPE_NIL, usage=PROPERTY_USAGE_GROUP})
	ret.push_back({name = "celestials_calculations", type=TYPE_INT, hint=PROPERTY_HINT_ENUM, hint_string="Simple, Realistic"})
	ret.push_back({name = "compute_moon_coords", type=TYPE_BOOL})

	if celestials_calculations == 0 && compute_moon_coords:
		ret.push_back({name = "moon_coords_offset", type=TYPE_VECTOR2})
		
	ret.push_back({name = "compute_deep_space_coords", type=TYPE_BOOL})
	ret.push_back({name = "latitude", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="-90.0, 90.0"})
	ret.push_back({name = "longitude", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="-180.0, 180.0"})
	ret.push_back({name = "utc", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="-12.0, 12.0"})
	ret.push_back({name = "celestials_update_time", type=TYPE_REAL})
	
	return ret