public static double Graviolis(Vessel v) { double dist = Vector3d.Distance(v.GetWorldPos3D(), Lib.GetParentSun(v.mainBody).position); double au = dist / FlightGlobals.GetHomeBody().orbit.semiMajorAxis; return(1.0 - Math.Min(AU, 1.0)); // 0 at 1AU -> 1 at sun position }
// ctor: deserialize public RadiationBody(ConfigNode node, Dictionary <string, RadiationModel> models, CelestialBody body) { name = Lib.ConfigValue(node, "name", ""); radiation_inner = Lib.ConfigValue(node, "radiation_inner", 0.0) / 3600.0; radiation_inner_gradient = Lib.ConfigValue(node, "radiation_inner_gradient", 3.3); radiation_outer = Lib.ConfigValue(node, "radiation_outer", 0.0) / 3600.0; radiation_outer_gradient = Lib.ConfigValue(node, "radiation_outer_gradient", 2.2); radiation_pause = Lib.ConfigValue(node, "radiation_pause", 0.0) / 3600.0; radiation_surface = Lib.ConfigValue(node, "radiation_surface", -1.0) / 3600.0; solar_cycle = Lib.ConfigValue(node, "solar_cycle", -1.0); solar_cycle_offset = Lib.ConfigValue(node, "solar_cycle_offset", 0.0); geomagnetic_pole_lat = Lib.ConfigValue(node, "geomagnetic_pole_lat", 90.0f); geomagnetic_pole_lon = Lib.ConfigValue(node, "geomagnetic_pole_lon", 0.0f); geomagnetic_offset = Lib.ConfigValue(node, "geomagnetic_offset", 0.0f); reference = Lib.ConfigValue(node, "reference", Lib.GetParentSun(body).flightGlobalsIndex); // get the radiation environment if (!models.TryGetValue(Lib.ConfigValue(node, "radiation_model", ""), out model)) { model = RadiationModel.none; } // get the body this.body = body; float lat = (float)(geomagnetic_pole_lat * Math.PI / 180.0); float lon = (float)(geomagnetic_pole_lon * Math.PI / 180.0); float x = Mathf.Cos(lat) * Mathf.Cos(lon); float y = Mathf.Sin(lat); float z = Mathf.Cos(lat) * Mathf.Sin(lon); geomagnetic_pole = new Vector3(x, y, z).normalized; if (Lib.IsSun(body)) { // suns without a solar cycle configuration default to a cycle of 6 years // (set to 0 if you really want none) if (solar_cycle < 0) { solar_cycle = Lib.HoursInDay * 3600 * Lib.DaysInYear * 6; } // add a rather nominal surface radiation for suns that have no config // for comparison: the stock kerbin sun has a surface radiation of 47 rad/h, which gives 0.01 rad/h near Kerbin // (set to 0 if you really want none) if (radiation_surface < 0) { radiation_surface = 10.0 * 3600.0; } } // calculate point emitter strength r0 at center of body if (radiation_surface > 0) { radiation_r0 = radiation_surface * 4 * Math.PI * body.Radius * body.Radius; } }
/// <summary>Estimated solar flux from the first parent sun of the given body, including other neighbouring stars/suns (binary systems handling)</summary> /// <param name="body"></param> /// <param name="worstCase">if true, we use the largest distance between the body and the sun</param> /// <param name="mainSun"></param> /// <param name="mainSunDirection"></param> /// <param name="mainSunDistance"></param> /// <returns></returns> public static double SolarFluxAtBody(CelestialBody body, bool worstCase, out CelestialBody mainSun, out Vector3d mainSunDirection, out double mainSunDistance) { // get first parent sun mainSun = Lib.GetParentSun(body); // get direction and distance mainSunDirection = (mainSun.position - body.position).normalized; if (worstCase) { mainSunDistance = Sim.Apoapsis(Lib.GetParentPlanet(body)) - mainSun.Radius - body.Radius; } else { mainSunDistance = Sim.SunDistance(body.position, mainSun); } // get solar flux int mainSunIndex = mainSun.flightGlobalsIndex; Sim.SunData mainSunData = Sim.suns.Find(pr => pr.bodyIndex == mainSunIndex); double solarFlux = mainSunData.SolarFlux(mainSunDistance); // multiple suns handling (binary systems...) foreach (Sim.SunData otherSun in Sim.suns) { if (otherSun.body == mainSun) { continue; } Vector3d otherSunDir = (otherSun.body.position - body.position).normalized; double otherSunDist; if (worstCase) { otherSunDist = Sim.Apoapsis(Lib.GetParentPlanet(body)) - otherSun.body.Radius; } else { otherSunDist = Sim.SunDistance(body.position, otherSun.body); } // account only for other suns that have approximatively the same direction (+/- 30°), discard the others if (Vector3d.Angle(otherSunDir, mainSunDirection) > 30.0) { continue; } solarFlux += otherSun.SolarFlux(otherSunDist); } return(solarFlux); }
private static void RadiationLevels(CelestialBody body, out string inner, out string outer, out string pause, out double activity, out double cycle) { // TODO cache this information somewhere var rb = Radiation.Info(body); double rad = Settings.ExternRadiation / 3600.0; var rbSun = Radiation.Info(Lib.GetParentSun(body)); rad += rbSun.radiation_pause; if (rb.inner_visible) { inner = rb.model.has_inner ? "~" + Lib.HumanReadableRadiation(Math.Max(0, rad + rb.radiation_inner)) : "n/a"; } else { inner = "unknown"; } if (rb.outer_visible) { outer = rb.model.has_outer ? "~" + Lib.HumanReadableRadiation(Math.Max(0, rad + rb.radiation_outer)) : "n/a"; } else { outer = "unknown"; } if (rb.pause_visible) { pause = rb.model.has_pause ? "∆" + Lib.HumanReadableRadiation(Math.Abs(rb.radiation_pause)) : "n/a"; } else { pause = "unknown"; } activity = -1; cycle = rb.solar_cycle; if (cycle > 0) { activity = rb.SolarActivity(); } }
// calculate irradiance in W/m2 from solar flux reflected on a celestial body in direction of the vessel public static double AlbedoFlux(CelestialBody body, Vector3d pos) { CelestialBody sun = Lib.GetParentSun(body); Vector3d sun_dir = sun.position - body.position; double sun_dist = sun_dir.magnitude; sun_dir /= sun_dist; sun_dist -= sun.Radius; Vector3d body_dir = pos - body.position; double body_dist = body_dir.magnitude; body_dir /= body_dist; body_dist -= body.Radius; // used to scale with distance double d = Math.Min((body.Radius + body.atmosphereDepth) / (body.Radius + body_dist), 1.0); return(suns.Find(p => p.body == sun).SolarFlux(sun_dist) // solar radiation * body.albedo // reflected * Math.Max(0.0, Vector3d.Dot(sun_dir, body_dir)) // clamped cosine * d * d); // scale with distance }
internal static void CreateStorm(StormData bd, CelestialBody body, double distanceToSun) { // do nothing if storms are disabled if (!Features.SpaceWeather) { return; } var now = Planetarium.GetUniversalTime(); if (bd.storm_generation < now) { var sun = Lib.GetParentSun(body); var avgDuration = PreferencesRadiation.Instance.AvgStormDuration; // retry after 3 * average storm duration + jitter (to avoid recalc spikes) bd.storm_generation = now + avgDuration * 3 + avgDuration * Lib.RandomDouble(); var rb = Radiation.Info(sun); var activity = rb.solar_cycle > 0 ? rb.SolarActivity() : 1.0; if (Lib.RandomDouble() < activity * PreferencesRadiation.Instance.stormFrequency) { // storm duration depends on current solar activity bd.storm_duration = avgDuration / 2.0 + avgDuration * activity * 2; // if further out, the storm lasts longer (but is weaker) bd.storm_duration /= Storm_frequency(distanceToSun); // set a start time to give enough time for warning bd.storm_time = now + Time_to_impact(distanceToSun); // delay next storm generation by duration of this one bd.storm_generation += bd.storm_duration; // add a random error to the estimated storm duration if we don't observe the sun too well var error = bd.storm_duration * 3 * Lib.RandomDouble() * (1 - sun_observation_quality); bd.displayed_duration = bd.storm_duration + error; // show warning message only if you're lucky... bd.display_warning = Lib.RandomFloat() < sun_observation_quality; #if DEBUG_RADIATION Lib.Log("Storm on " + body + " will start in " + Lib.HumanReadableDuration(bd.storm_time - now) + " and last for " + Lib.HumanReadableDuration(bd.storm_duration)); } else { Lib.Log("No storm on " + body + ", will retry in " + Lib.HumanReadableDuration(bd.storm_generation - now)); #endif } } if (bd.storm_time + bd.storm_duration < now) { // storm is over bd.Reset(); } else if (bd.storm_time < now && bd.storm_time + bd.storm_duration > now) { // storm in progress bd.storm_state = 2; } else if (bd.storm_time > now) { // storm incoming bd.storm_state = 1; } }
// return irradiance from the surface of a body in W/m2 public static double BodyFlux(CelestialBody body, double altitude) { CelestialBody sun = Lib.GetParentSun(body); Vector3d sun_dir = sun.position - body.position; double sun_dist = sun_dir.magnitude; sun_dir /= sun_dist; sun_dist -= sun.Radius; // heat capacities, in J/(g K) const double water_k = 4.181; const double regolith_k = 0.67; const double hydrogen_k = 14.300; const double helium_k = 5.193; const double oxygen_k = 0.918; const double silicum_k = 0.703; const double aluminium_k = 0.897; const double iron_k = 0.412; const double co2_k = 0.839; const double nitrogen_k = 1.040; const double argon_k = 0.520; const double earth_surf_k = oxygen_k * 0.54 + silicum_k * 0.31 + aluminium_k * 0.09 + iron_k * 0.06; const double earth_atmo_k = nitrogen_k * 0.78 + oxygen_k * 0.21 + argon_k * 0.01; const double hell_atmo_k = co2_k * 0.96 + nitrogen_k * 0.04; const double gas_giant_k = hydrogen_k * 0.75 + helium_k * 0.25; // proportion of flux not absorbed by atmosphere double atmo_factor = AtmosphereFactor(body, 0.7071); // try to determine if this is a gas giant // - old method: density less than 20% of home planet bool is_gas_giant = !body.hasSolidSurface; // try to determine if this is a runaway greenhouse planet bool is_hell = atmo_factor < 0.5; // store heat capacity coefficients double surf_k = 0.0; double atmo_k = 0.0; // deduce surface and atmosphere heat capacity coefficients if (is_gas_giant) { surf_k = gas_giant_k; atmo_k = gas_giant_k; } else { if (body.atmosphere) { surf_k = !body.ocean ? earth_surf_k : earth_surf_k * 0.29 + water_k * 0.71; atmo_k = !is_hell ? earth_atmo_k : hell_atmo_k; } else { surf_k = !body.ocean ? regolith_k : regolith_k * 0.5 + water_k * 0.5; } } // how much flux a part of surface is able to store, in J double surf_capacity = surf_k // heat capacity, in J/(g K) * body.Radius / 6000.0 // volume of ground considered, 1x1xN m (100 m^3 at kerbin) * body.Density // convert to grams / 4.0; // lighter matter on surface compared to overall density // how much flux the atmosphere is able to store, in J double atmo_capacity = atmo_k * body.atmospherePressureSeaLevel * 1000.0 * SurfaceGravity(body); // solar flux striking the body double solar_flux = suns.Find(p => p.body == sun).SolarFlux(sun_dist); // duration of lit and unlit periods double half_day = body.solarDayLength * 0.5; // flux stored by surface during daylight, and re-emitted during whole day double surf_flux = Math.Min ( solar_flux // incoming flux * (1.0 - body.albedo) // not reflected * atmo_factor // not absorbed by atmosphere * 0.7071 // clamped cosine average * half_day, // accumulated during daylight period surf_capacity // clamped to storage capacity ) / body.solarDayLength; // released during whole day // flux stored by atmosphere during daylight, and re-emitted during whole day double atmo_flux = Math.Min ( solar_flux // incoming flux * (1.0 - body.albedo) // not reflected * (1.0 - atmo_factor) // absorbed by atmosphere * half_day, // accumulated during daylight period atmo_capacity // clamped to storage capacity ) / body.solarDayLength; // released during whole day // used to scale with distance double d = Math.Min((body.Radius + body.atmosphereDepth) / (body.Radius + altitude), 1.0); // return radiative cooling flux from the body return((surf_flux + atmo_flux) * d * d); }