Exemplo n.º 1
0
        // return the surface radiation for the body specified (used by body info panel)
        public static double ComputeSurface(CelestialBody b, double gamma_transparency)
        {
            // store stuff
            Space   gsm;
            Vector3 p;
            float   D;

            // transform to local space once
            Vector3d position = ScaledSpace.LocalToScaledSpace(b.position);

            // accumulate radiation
            double        radiation = 0.0;
            CelestialBody body      = b;

            while (body != null)
            {
                RadiationBody  rb = Info(body);
                RadiationModel mf = rb.model;
                if (mf.Has_field())
                {
                    // generate radii-normalized GSM space
                    gsm = Gsm_space(rb, true);

                    // move the poing in GSM space
                    p = gsm.Transform_in(position);

                    // accumulate radiation and determine pause/belt flags
                    if (mf.has_inner)
                    {
                        D          = mf.Inner_func(p);
                        radiation += Lib.Clamp(D / -0.0666f, 0.0f, 1.0f) * rb.radiation_inner;
                    }
                    if (mf.has_outer)
                    {
                        D          = mf.Outer_func(p);
                        radiation += Lib.Clamp(D / -0.0333f, 0.0f, 1.0f) * rb.radiation_outer;
                    }
                    if (mf.has_pause)
                    {
                        gsm        = Gsm_space(rb, false);
                        p          = gsm.Transform_in(position);
                        D          = mf.Pause_func(p);
                        radiation += Lib.Clamp(D / -0.1332f, 0.0f, 1.0f) * rb.radiation_pause;
                    }
                }

                // avoid loops in the chain
                body = (body.referenceBody != null && body.referenceBody.referenceBody == body) ? null : body.referenceBody;
            }

            // add extern radiation
            radiation += PreferencesStorm.Instance.ExternRadiation;

            // clamp radiation to positive range
            // note: we avoid radiation going to zero by using a small positive value
            radiation = Math.Max(radiation, Nominal);

            // return radiation, scaled by gamma transparency if inside atmosphere
            return(radiation * gamma_transparency);
        }
Exemplo n.º 2
0
        /// <summary>return true if the given body has a belt or a magnetosphere (doesn't matter if visible or not)</summary>
        public static bool HasMagneticField(CelestialBody body)
        {
            if (!Features.Radiation)
            {
                return(false);
            }
            RadiationBody rb = KERBALISM.Radiation.Info(body);

            return(rb.model.Has_field());
        }
Exemplo n.º 3
0
        /// <summary>set visibility of the inner radiation belt</summary>
        public static void SetMagnetopauseVisible(CelestialBody body, bool visible)
        {
            if (!Features.Radiation)
            {
                return;
            }
            RadiationBody rb = KERBALISM.Radiation.Info(body);

            rb.pause_visible = visible;
        }
Exemplo n.º 4
0
        /// <summary>return true if the given body has a magnetopause that is visible</summary>
        public static bool IsMagnetopauseVisible(CelestialBody body)
        {
            if (!Features.Radiation)
            {
                return(false);
            }
            RadiationBody rb = KERBALISM.Radiation.Info(body);

            return(rb.model.has_pause && rb.pause_visible);
        }
Exemplo n.º 5
0
        /// <summary>set visibility of the inner radiation belt</summary>
        public static void SetOuterBeltVisible(CelestialBody body, bool visible)
        {
            if (!Features.Radiation)
            {
                return;
            }
            RadiationBody rb = KERBALISM.Radiation.Info(body);

            rb.outer_visible = visible;
        }
Exemplo n.º 6
0
        /// <summary>return true if the given body has an outer radiation belt that is visible</summary>
        public static bool IsOuterBeltVisible(CelestialBody body)
        {
            if (!Features.Radiation)
            {
                return(false);
            }
            RadiationBody rb = KERBALISM.Radiation.Info(body);

            return(rb.model.has_outer && rb.outer_visible);
        }
Exemplo n.º 7
0
        /// <summary>return true if the given body has an outer radiation belt (doesn't matter if visible or not)</summary>
        public static bool HasOuterBelt(CelestialBody body)
        {
            if (!Features.Radiation)
            {
                return(false);
            }
            RadiationBody rb = KERBALISM.Radiation.Info(body);

            return(rb.model.has_outer);
        }
Exemplo n.º 8
0
        /// <summary> Returns the radiation emitted by the body at the center, adjusted by solar activity cycle </summary>
        private static double RadiationR0(RadiationBody rb)
        {
            // for easier configuration, the radiation model sets the radiation on the surface of the body.
            // from there, it decreases according to the inverse square law with distance from the surface.

            var r0 = rb.radiation_r0; // precomputed

            // if there is a solar cycle, add a bit of radiation variation relative to current activity

            if (rb.solar_cycle > 0)
            {
                var activity = rb.SolarActivity() * 0.3;
                r0 = r0 + r0 * activity;
            }

            return(r0);
        }
Exemplo n.º 9
0
        // return the total environent radiation at position specified
        public static double Compute(Vessel v, Vector3d position, double gamma_transparency, double sunlight, out bool blackout,
                                     out bool magnetosphere, out bool inner_belt, out bool outer_belt, out bool interstellar)
        {
            // prepare out parameters
            blackout      = false;
            magnetosphere = false;
            inner_belt    = false;
            outer_belt    = false;
            interstellar  = false;

            // no-op when Radiation is disabled
            if (!Features.Radiation)
            {
                return(0.0);
            }

            // store stuff
            Space   gsm;
            Vector3 p;
            float   D;

            // transform to local space once
            position = ScaledSpace.LocalToScaledSpace(position);

            // accumulate radiation
            double        radiation = 0.0;
            CelestialBody body      = v.mainBody;

            while (body != null)
            {
                RadiationBody  rb = Info(body);
                RadiationModel mf = rb.model;
                if (mf.Has_field())
                {
                    // generate radii-normalized GSM space
                    gsm = Gsm_space(rb.body, FlightGlobals.Bodies[rb.reference]);

                    // move the poing in GSM space
                    p = gsm.Transform_in(position);

                    // accumulate radiation and determine pause/belt flags
                    if (mf.has_inner)
                    {
                        D           = mf.Inner_func(p);
                        radiation  += Lib.Clamp(D / -0.0666f, 0.0f, 1.0f) * rb.radiation_inner;
                        inner_belt |= D < 0.0f;
                    }
                    if (mf.has_outer)
                    {
                        D           = mf.Outer_func(p);
                        radiation  += Lib.Clamp(D / -0.0333f, 0.0f, 1.0f) * rb.radiation_outer;
                        outer_belt |= D < 0.0f;
                    }
                    if (mf.has_pause)
                    {
                        D              = mf.Pause_func(p);
                        radiation     += Lib.Clamp(D / -0.1332f, 0.0f, 1.0f) * rb.radiation_pause;
                        magnetosphere |= D < 0.0f && rb.body.flightGlobalsIndex != 0;                        //< ignore heliopause
                        interstellar  |= D > 0.0f && rb.body.flightGlobalsIndex == 0;                        //< outside heliopause
                    }
                }

                // avoid loops in the chain
                body = (body.referenceBody != null && body.referenceBody.referenceBody == body) ? null : body.referenceBody;
            }

            // add extern radiation
            radiation += PreferencesStorm.Instance.ExternRadiation;

            // add emitter radiation
            radiation += Emitter.Total(v);

            // if there is a storm in progress
            if (Storm.InProgress(v))
            {
                // inside a magnetopause (except heliosphere), blackout the signal
                // outside, add storm radiations modulated by sun visibility
                if (magnetosphere)
                {
                    blackout = true;
                }
                else
                {
                    radiation += PreferencesStorm.Instance.StormRadiation * sunlight;
                }
            }

            // clamp radiation to positive range
            // note: we avoid radiation going to zero by using a small positive value
            radiation = Math.Max(radiation, Nominal);

            // return radiation, scaled by gamma transparency if inside atmosphere
            return(radiation * gamma_transparency);
        }
Exemplo n.º 10
0
        // render the fields of the interesting body
        public static void Render()
        {
            // get interesting body
            CelestialBody body = Interesting_body();

            // maintain visualization modes
            if (body == null)
            {
                show_inner = false;
                show_outer = false;
                show_pause = false;
            }
            else
            {
                if (Input.GetKeyDown(KeyCode.Keypad0))
                {
                    if (show_inner || show_outer || show_pause)
                    {
                        show_inner = false;
                        show_outer = false;
                        show_pause = false;
                    }
                    else
                    {
                        show_inner = true;
                        show_outer = true;
                        show_pause = true;
                    }
                }
                if (Input.GetKeyDown(KeyCode.Keypad1))
                {
                    show_inner = true;
                    show_outer = false;
                    show_pause = false;
                }
                if (Input.GetKeyDown(KeyCode.Keypad2))
                {
                    show_inner = false;
                    show_outer = true;
                    show_pause = false;
                }
                if (Input.GetKeyDown(KeyCode.Keypad3))
                {
                    show_inner = false;
                    show_outer = false;
                    show_pause = true;
                }
            }


            // if there is an active body, and at least one of the modes is active
            if (body != null && (show_inner || show_outer || show_pause))
            {
                // if we don't know if preprocessing is completed
                if (preprocess_thread != null)
                {
                    // if the preprocess thread has not done yet
                    if (preprocess_thread.IsAlive)
                    {
                        // disable all modes
                        show_inner = false;
                        show_outer = false;
                        show_pause = false;

                        // tell the user and do nothing
                        Message.Post("<color=#00ffff><b>Fitting particles to signed distance fields</b></color>", "Come back in a minute");
                        return;
                    }

                    // wait for particle-fitting thread to cleanup
                    preprocess_thread.Join();

                    // preprocessing is complete
                    preprocess_thread = null;
                }


                // load and configure shader
                if (mat == null)
                {
                    if (!Settings.LowQualityRendering)
                    {
                        // load shader
                        mat = Lib.GetShader("MiniParticle");

                        // configure shader
                        mat.SetColor("POINT_COLOR", new Color(0.33f, 0.33f, 0.33f, 0.1f));
                    }
                    else
                    {
                        // load shader
                        mat = Lib.GetShader("PointParticle");

                        // configure shader
                        mat.SetColor("POINT_COLOR", new Color(0.33f, 0.33f, 0.33f, 0.1f));
                        mat.SetFloat("POINT_SIZE", 4.0f);
                    }
                }

                // generate radii-normalized GMS space
                RadiationBody rb  = Info(body);
                Space         gsm = Gsm_space(rb.body, FlightGlobals.Bodies[rb.reference]);

#if DEBUG               // show axis
                LineRenderer.Commit(gsm.origin, gsm.origin + gsm.x_axis * gsm.scale * 5.0f, Color.red);
                LineRenderer.Commit(gsm.origin, gsm.origin + gsm.y_axis * gsm.scale * 5.0f, Color.green);
                LineRenderer.Commit(gsm.origin, gsm.origin + gsm.z_axis * gsm.scale * 5.0f, Color.blue);
#endif
                // get magnetic field data
                RadiationModel mf = Info(body).model;

                // enable material
                mat.SetPass(0);

                // render active body fields
                Matrix4x4 m = gsm.Look_at();
                if (show_inner && mf.has_inner)
                {
                    mf.inner_pmesh.Render(m);
                }
                if (show_outer && mf.has_outer)
                {
                    mf.outer_pmesh.Render(m);
                }
                if (show_pause && mf.has_pause)
                {
                    mf.pause_pmesh.Render(m);
                }
            }
        }
Exemplo n.º 11
0
        // generate gsm-space frame of reference
        // - origin is at body position
        // - the x-axis point to reference body
        // - the rotation axis is used as y-axis initial guess
        // - the space is then orthonormalized
        // - if the reference body is the same as the body,
        //   the galactic rotation vector is used as x-axis instead
        public static Space Gsm_space(RadiationBody rb, bool tilted)
        {
            CelestialBody body      = rb.body;
            CelestialBody reference = FlightGlobals.Bodies[rb.reference];

            Space gsm;

            gsm.origin = ScaledSpace.LocalToScaledSpace(body.position);
            gsm.scale  = ScaledSpace.InverseScaleFactor * (float)body.Radius;
            if (body != reference)
            {
                gsm.x_axis = ((Vector3)ScaledSpace.LocalToScaledSpace(reference.position) - gsm.origin).normalized;
                if (!tilted)
                {
                    gsm.y_axis = body.RotationAxis;                                //< initial guess
                    gsm.z_axis = Vector3.Cross(gsm.x_axis, gsm.y_axis).normalized;
                    gsm.y_axis = Vector3.Cross(gsm.z_axis, gsm.x_axis).normalized; //< orthonormalize
                }
                else
                {
                    /* "Do not try and tilt the planet, that's impossible.
                     * Instead, only try to realize the truth...there is no tilt.
                     * Then you'll see that it is not the planet that tilts, it is
                     * the rest of the universe."
                     *
                     * - The Matrix
                     *
                     *
                     * the orbits are inclined (with respect to the equator of the
                     * Earth), but all axes are parallel. and aligned with the unity
                     * world z axis. or is it y? whatever, KSP uses two conventions
                     * in different places.
                     * if you use Principia, the current main body (or if there is
                     * none, e.g. in the space centre or tracking station, the home
                     * body) is not tilted (its axis is the unity vertical.
                     * you can fetch the full orientation (tilt and rotation) of any
                     * body (including the current main body) in the current unity
                     * frame (which changes of course, because sometimes KSP uses a
                     * rotating frame, and because Principia tilts the universe
                     * differently if the current main body changes) as the
                     * orientation of the scaled space body
                     *
                     * body.scaledBody.transform.rotation or something along those lines
                     *
                     * - egg
                     */

                    Vector3    pole     = rb.geomagnetic_pole;
                    Quaternion rotation = body.scaledBody.transform.rotation;
                    gsm.y_axis = (rotation * pole).normalized;

                    gsm.z_axis = Vector3.Cross(gsm.x_axis, gsm.y_axis).normalized;
                    gsm.x_axis = Vector3.Cross(gsm.y_axis, gsm.z_axis).normalized;                     //< orthonormalize
                }
            }
            else
            {
                // galactic
                gsm.x_axis = new Vector3(1.0f, 0.0f, 0.0f);
                gsm.y_axis = new Vector3(0.0f, 1.0f, 0.0f);
                gsm.z_axis = new Vector3(0.0f, 0.0f, 1.0f);
            }

            gsm.origin = gsm.origin + gsm.y_axis * (gsm.scale * rb.geomagnetic_offset);

            return(gsm);
        }
Exemplo n.º 12
0
        // return the surface radiation for the body specified (used by body info panel)
        public static double ComputeSurface(CelestialBody b, double gamma_transparency)
        {
            if (!Features.Radiation)
            {
                return(0.0);
            }

            // store stuff
            Space   gsm;
            Vector3 p;
            double  D;

            // transform to local space once
            Vector3d position = ScaledSpace.LocalToScaledSpace(b.position);

            // accumulate radiation
            double        radiation = 0.0;
            CelestialBody body      = b;

            while (body != null)
            {
                RadiationBody  rb = Info(body);
                RadiationModel mf = rb.model;

                var activity = rb.SolarActivity(false);

                if (mf.Has_field())
                {
                    // generate radii-normalized GSM space
                    gsm = Gsm_space(rb, true);

                    // move the poing in GSM space
                    p = gsm.Transform_in(position);

                    // accumulate radiation and determine pause/belt flags
                    if (mf.has_inner)
                    {
                        D = mf.Inner_func(p);
                        // allow for radiation field to grow/shrink with solar activity
                        D -= activity * 0.25 / mf.inner_radius;

                        var r = RadiationInBelt(D, mf.inner_radius, rb.radiation_inner_gradient);
                        radiation += r * rb.radiation_inner * (1 + activity * 0.3);
                    }
                    if (mf.has_outer)
                    {
                        D = mf.Outer_func(p);
                        // allow for radiation field to grow/shrink with solar activity
                        D -= activity * 0.25 / mf.outer_radius;

                        var r = RadiationInBelt(D, mf.outer_radius, rb.radiation_outer_gradient);
                        radiation += r * rb.radiation_outer * (1 + activity * 0.3);
                    }
                    if (mf.has_pause)
                    {
                        gsm        = Gsm_space(rb, false);
                        p          = gsm.Transform_in(position);
                        D          = mf.Pause_func(p);
                        radiation += Lib.Clamp(D / -0.1332f, 0.0f, 1.0f) * rb.RadiationPause();
                    }
                }

                if (rb.radiation_surface > 0 && body != b)
                {
                    // add surface radiation emitted from other body
                    double distance = (b.position - body.position).magnitude;
                    var    r0       = RadiationR0(rb);
                    var    r1       = DistanceRadiation(r0, distance);

                    // Lib.Log("Surface radiation on " + b + " from " + body + ": " + Lib.HumanReadableRadiation(r1) + " distance " + distance);

                    // clamp to max. surface radiation. when loading on a rescaled system, the vessel can appear to be within the sun for a few ticks
                    radiation += Math.Min(r1, rb.radiation_surface);
                }

                // avoid loops in the chain
                body = (body.referenceBody != null && body.referenceBody.referenceBody == body) ? null : body.referenceBody;
            }

            // add extern radiation
            radiation += Settings.ExternRadiation / 3600.0;

            // Lib.Log("Radiation subtotal on " + b + ": " + Lib.HumanReadableRadiation(radiation) + ", gamma " + gamma_transparency);

            // scale radiation by gamma transparency if inside atmosphere
            radiation *= gamma_transparency;
            // Lib.Log("srf scaled on " + b + ": " + Lib.HumanReadableRadiation(radiation));

            // add surface radiation of the body itself
            RadiationBody bodyInfo = Info(b);

            // clamp to max. bodyInfo.radiation_surface to avoid extreme radiation effects while loading a vessel on rescaled systems
            radiation += Math.Min(bodyInfo.radiation_surface, DistanceRadiation(RadiationR0(bodyInfo), b.Radius));

            // Lib.Log("Radiation on " + b + ": " + Lib.HumanReadableRadiation(radiation) + ", own surface radiation " + Lib.HumanReadableRadiation(DistanceRadiation(RadiationR0(Info(b)), b.Radius)));

            // Lib.Log("radiation " + radiation + " nominal " + Nominal);

            // clamp radiation to positive range
            // note: we avoid radiation going to zero by using a small positive value
            radiation = Math.Max(radiation, Nominal);

            return(radiation);
        }
Exemplo n.º 13
0
        // return the total environent radiation at position specified
        public static double Compute(Vessel v, Vector3d position, double gamma_transparency, double sunlight, out bool blackout,
                                     out bool magnetosphere, out bool inner_belt, out bool outer_belt, out bool interstellar, out double shieldedRadiation)
        {
            // prepare out parameters
            blackout          = false;
            magnetosphere     = false;
            inner_belt        = false;
            outer_belt        = false;
            interstellar      = false;
            shieldedRadiation = 0.0;

            // no-op when Radiation is disabled
            if (!Features.Radiation)
            {
                return(0.0);
            }

            // store stuff
            Space   gsm;
            Vector3 p;
            double  D;
            double  r;

            // accumulate radiation
            double        radiation = 0.0;
            CelestialBody body      = v.mainBody;

            while (body != null)
            {
                // Compute radiation values from overlapping 3d fields (belts + magnetospheres)

                RadiationBody  rb = Info(body);
                RadiationModel mf = rb.model;

                // activity is [-0.15..1.05]
                var activity = rb.SolarActivity(false);

                if (mf.Has_field())
                {
                    // transform to local space once
                    var scaled_position = ScaledSpace.LocalToScaledSpace(position);

                    // generate radii-normalized GSM space
                    gsm = Gsm_space(rb, true);

                    // move the point in GSM space
                    p = gsm.Transform_in(scaled_position);

                    // accumulate radiation and determine pause/belt flags
                    if (mf.has_inner)
                    {
                        D           = mf.Inner_func(p);
                        inner_belt |= D < 0;

                        // allow for radiation field to grow/shrink with solar activity
                        D         -= activity * 0.25 / mf.inner_radius;
                        r          = RadiationInBelt(D, mf.inner_radius, rb.radiation_inner_gradient);
                        radiation += r * rb.radiation_inner * (1 + activity * 0.3);
                    }
                    if (mf.has_outer)
                    {
                        D           = mf.Outer_func(p);
                        outer_belt |= D < 0;

                        // allow for radiation field to grow/shrink with solar activity
                        D         -= activity * 0.25 / mf.outer_radius;
                        r          = RadiationInBelt(D, mf.outer_radius, rb.radiation_outer_gradient);
                        radiation += r * rb.radiation_outer * (1 + activity * 0.3);
                    }
                    if (mf.has_pause)
                    {
                        gsm = Gsm_space(rb, false);
                        p   = gsm.Transform_in(scaled_position);
                        D   = mf.Pause_func(p);

                        radiation += Lib.Clamp(D / -0.1332f, 0.0f, 1.0f) * rb.RadiationPause();

                        magnetosphere |= D < 0.0f && !Lib.IsSun(rb.body); //< ignore heliopause
                        interstellar  |= D > 0.0f && Lib.IsSun(rb.body);  //< outside heliopause
                    }
                }

                if (rb.radiation_surface > 0 && body != v.mainBody)
                {
                    Vector3d direction;
                    double   distance;
                    if (Sim.IsBodyVisible(v, position, body, v.KerbalismData().EnvVisibleBodies, out direction, out distance))
                    {
                        var r0 = RadiationR0(rb);
                        var r1 = DistanceRadiation(r0, distance);

                        // clamp to max. surface radiation. when loading on a rescaled system, the vessel can appear to be within the sun for a few ticks
                        radiation += Math.Min(r1, rb.radiation_surface);
#if DEBUG_RADIATION
                        if (v.loaded)
                        {
                            Lib.Log("Radiation " + v + " from surface of " + body + ": " + Lib.HumanReadableRadiation(radiation) + " gamma: " + Lib.HumanReadableRadiation(r1));
                        }
#endif
                    }
                }

                // avoid loops in the chain
                body = (body.referenceBody != null && body.referenceBody.referenceBody == body) ? null : body.referenceBody;
            }

            // add extern radiation
            radiation += Settings.ExternRadiation / 3600.0;

#if DEBUG_RADIATION
            if (v.loaded)
            {
                Lib.Log("Radiation " + v + " extern: " + Lib.HumanReadableRadiation(radiation) + " gamma: " + Lib.HumanReadableRadiation(Settings.ExternRadiation));
            }
#endif

            // apply gamma transparency if inside atmosphere
            radiation *= gamma_transparency;

#if DEBUG_RADIATION
            if (v.loaded)
            {
                Lib.Log("Radiation " + v + " after gamma: " + Lib.HumanReadableRadiation(radiation) + " transparency: " + gamma_transparency);
            }
#endif
            // add surface radiation of the body itself
            if (Lib.IsSun(v.mainBody) && v.altitude < v.mainBody.Radius)
            {
                if (v.altitude > v.mainBody.Radius)
                {
                    radiation += DistanceRadiation(RadiationR0(Info(v.mainBody)), v.altitude);
                }
            }

#if DEBUG_RADIATION
            if (v.loaded)
            {
                Lib.Log("Radiation " + v + " from current main body: " + Lib.HumanReadableRadiation(radiation) + " gamma: " + Lib.HumanReadableRadiation(DistanceRadiation(RadiationR0(Info(v.mainBody)), v.altitude)));
            }
#endif

            shieldedRadiation = radiation;

            // if there is a storm in progress
            if (Storm.InProgress(v))
            {
                // inside a magnetopause (except heliosphere), blackout the signal
                // outside, add storm radiations modulated by sun visibility
                if (magnetosphere)
                {
                    blackout = true;
                }
                else
                {
                    var vd = v.KerbalismData();

                    var activity = Info(vd.EnvMainSun.SunData.body).SolarActivity(false) / 2.0;
                    var strength = PreferencesRadiation.Instance.StormRadiation * sunlight * (activity + 0.5);

                    radiation         += strength;
                    shieldedRadiation += vd.EnvHabitatInfo.AverageHabitatRadiation(strength);
                }
            }

            // add emitter radiation after atmosphere transparency
            var emitterRadiation = Emitter.Total(v);
            radiation         += emitterRadiation;
            shieldedRadiation += emitterRadiation;

#if DEBUG_RADIATION
            if (v.loaded)
            {
                Lib.Log("Radiation " + v + " after emitters: " + Lib.HumanReadableRadiation(radiation) + " shielded " + Lib.HumanReadableRadiation(shieldedRadiation));
            }
#endif

            // for EVAs, add the effect of nearby emitters
            if (v.isEVA)
            {
                var nearbyEmitters = Emitter.Nearby(v);
                radiation         += nearbyEmitters;
                shieldedRadiation += nearbyEmitters;
#if DEBUG_RADIATION
                if (v.loaded)
                {
                    Lib.Log("Radiation " + v + " nearby emitters " + Lib.HumanReadableRadiation(nearbyEmitters));
                }
#endif
            }

            var passiveShielding = PassiveShield.Total(v);
            shieldedRadiation -= passiveShielding;

#if DEBUG_RADIATION
            if (v.loaded)
            {
                Lib.Log("Radiation " + v + " passiveShielding " + Lib.HumanReadableRadiation(passiveShielding));
            }
            if (v.loaded)
            {
                Lib.Log("Radiation " + v + " before clamp: " + Lib.HumanReadableRadiation(radiation) + " shielded " + Lib.HumanReadableRadiation(shieldedRadiation));
            }
#endif

            // clamp radiation to positive range
            // note: we avoid radiation going to zero by using a small positive value
            radiation         = Math.Max(radiation, Nominal);
            shieldedRadiation = Math.Max(shieldedRadiation, Nominal);

#if DEBUG_RADIATION
            if (v.loaded)
            {
                Lib.Log("Radiation " + v + " after clamp: " + Lib.HumanReadableRadiation(radiation) + " shielded " + Lib.HumanReadableRadiation(shieldedRadiation));
            }
#endif
            // return radiation
            return(radiation);
        }