/// <summary>
    /// Generates a new set of precipitation and snowfall map images.
    /// </summary>
    /// <param name="planet">The planet being mapped.</param>
    /// <param name="winterTemperatures">A winter temperature map.</param>
    /// <param name="summerTemperatues">A summer temperature map.</param>
    /// <param name="resolution">The vertical resolution.</param>
    /// <param name="steps">
    /// The number of maps to generate internally (representing evenly spaced "seasons" during a year,
    /// starting and ending at the winter solstice in the northern hemisphere).
    /// </param>
    /// <param name="temperatureProjection">
    /// <para>
    /// The map projection of the temperature maps. They must be the same.
    /// </para>
    /// <para>
    /// If left <see langword="null"/> an equirectangular projection of the full globe is
    /// assumed.
    /// </para>
    /// </param>
    /// <param name="projection">
    /// <para>
    /// The map projection options used.
    /// </para>
    /// <para>
    /// If left <see langword="null"/> an equirectangular projection of the full globe is
    /// produced.
    /// </para>
    /// </param>
    /// <returns>
    /// A set of precipitation and snowfall map images. Pixel luminosity indicates
    /// precipitation in mm/hr, relative to the <see cref="Atmosphere.MaxPrecipitation"/> of
    /// this planet's <see cref="Atmosphere"/>.
    /// </returns>
    public static (Image <L16>[] precipitationMaps, Image <L16>[] snowfallMaps) GetPrecipitationAndSnowfallMaps(
        this Planetoid planet,
        Image <L16> winterTemperatures,
        Image <L16> summerTemperatues,
        int resolution,
        int steps,
        MapProjectionOptions?temperatureProjection = null,
        MapProjectionOptions?projection            = null)
    {
        var options           = projection ?? MapProjectionOptions.Default;
        var precipitationMaps = new Image <L16> [steps];
        var snowMaps          = new Image <L16> [steps];

        if (planet.Atmosphere.MaxPrecipitation.IsNearlyZero())
        {
            var xResolution = (int)Math.Floor(resolution * options.AspectRatio);
            for (var i = 0; i < steps; i++)
            {
                precipitationMaps[i] = new Image <L16>(xResolution, resolution);
                snowMaps[i]          = new Image <L16>(xResolution, resolution);
            }
            return(precipitationMaps, snowMaps);
        }

        var noise1 = new FastNoise(planet.Seed4, 1.0, FastNoise.NoiseType.Simplex);
        var noise2 = new FastNoise(planet.Seed5, 3.0, FastNoise.NoiseType.SimplexFractal, octaves: 3);

        var proportionOfYear           = 1f / steps;
        var proportionOfYearAtMidpoint = 0f;
        var trueAnomaly          = planet.WinterSolsticeTrueAnomaly;
        var trueAnomalyPerSeason = DoubleConstants.TwoPi / steps;

        for (var i = 0; i < steps; i++)
        {
            var solarDeclination = planet.GetSolarDeclination(trueAnomaly);
            (precipitationMaps[i], snowMaps[i]) = SurfaceMapImage.GenerateMapImages(
                new[] { winterTemperatures, summerTemperatues },
                (lat, lon, temperature) =>
            {
                var precipitation = planet.GetPrecipitationNoise(
                    noise1,
                    noise2,
                    planet.LatitudeAndLongitudeToDoubleVector(lat, lon),
                    lat,
                    Planetoid.GetSeasonalLatitudeFromDeclination(lat, solarDeclination),
                    temperature * SurfaceMapImage.TemperatureScaleFactor,
                    out var snow);
                return(
                    precipitation / planet.Atmosphere.MaxPrecipitation,
                    snow / planet.Atmosphere.MaxSnowfall);
            },
    /// <summary>
    /// Calculates the atmospheric density for the given conditions, in kg/m³.
    /// </summary>
    /// <param name="planet">The mapped planet.</param>
    /// <param name="winterTemperatures">A winter temperature map.</param>
    /// <param name="summerTemperatures">A summer temperature map.</param>
    /// <param name="proportionOfYear">
    /// The proportion of a full year at which the map is to be generated, assuming a year
    /// begins and ends at the winter solstice in the northern hemisphere.
    /// </param>
    /// <param name="latitude">The latitude of the object.</param>
    /// <param name="longitude">The longitude of the object.</param>
    /// <param name="altitude">The altitude of the object.</param>
    /// <param name="surface">
    /// If <see langword="true"/> the determination is made for a location
    /// on the surface of the planetoid at the given elevation. Otherwise, the calculation is
    /// made for an elevation above the surface.
    /// </param>
    /// <param name="options">The map projection used.</param>
    /// <returns>The atmospheric density for the given conditions, in kg/m³.</returns>
    public static double GetAtmosphericDensity(
        this Planetoid planet,
        Image <L16> winterTemperatures,
        Image <L16> summerTemperatures,
        double proportionOfYear,
        double latitude,
        double longitude,
        double altitude,
        bool surface = true,
        MapProjectionOptions?options = null)
    {
        var surfaceTemp     = SurfaceMapImage.GetSurfaceTemperature(winterTemperatures, summerTemperatures, proportionOfYear, latitude, longitude, options);
        var tempAtElevation = planet.GetTemperatureAtElevation(surfaceTemp, altitude, surface);

        return(planet.Atmosphere.GetAtmosphericDensity(planet, tempAtElevation, altitude));
    }
    /// <summary>
    /// Generates an elevation map image for this planet.
    /// </summary>
    /// <param name="planet">The mapped planet.</param>
    /// <param name="resolution">The vertical resolution of the map.</param>
    /// <param name="options">
    /// <para>
    /// The map projection options used.
    /// </para>
    /// <para>
    /// If left <see langword="null"/> an equirectangular projection of the full globe is
    /// produced.
    /// </para>
    /// </param>
    /// <returns>An elevation map image for this planet.</returns>
    public static Image <L16> GetElevationMap(
        this Planetoid planet,
        int resolution,
        MapProjectionOptions?options = null)
    {
        if (planet.MaxElevation.IsNearlyZero())
        {
            return(SurfaceMapImage.GenerateZeroMapImage(resolution, options, true));
        }

        var noise1 = new FastNoise(planet.Seed1, 0.8, FastNoise.NoiseType.SimplexFractal, octaves: 6);
        var noise2 = new FastNoise(planet.Seed2, 0.6, FastNoise.NoiseType.SimplexFractal, FastNoise.FractalType.Billow, octaves: 6);
        var noise3 = new FastNoise(planet.Seed3, 1.2, FastNoise.NoiseType.Simplex);

        return(SurfaceMapImage.GenerateMapImage(
                   (lat, lon) => GetElevationNoise(noise1, noise2, noise3, planet.LatitudeAndLongitudeToDoubleVector(lat, lon)),
                   resolution,
                   options,
                   true));
    }
Exemple #4
0
    public void EarthlikePlanet()
    {
        // First run to ensure timed runs do not include any one-time initialization costs.
        _ = Planetoid.GetPlanetForSunlikeStar(out _);

        var stopwatch = new Stopwatch();

        stopwatch.Start();

        var planet = Planetoid.GetPlanetForSunlikeStar(out _);

        stopwatch.Stop();

        Assert.IsNotNull(planet);

        Console.WriteLine($"Planet generation time: {stopwatch.Elapsed:s'.'FFF} s");
        Console.WriteLine($"Radius: {planet!.Shape.ContainingRadius / 1000:N0} km");
        Console.WriteLine($"Surface area: {planet!.Shape.ContainingRadius.Square() * HugeNumberConstants.FourPi / 1000000:N0} km²");

        stopwatch.Restart();

        using (var elevationMap = planet.GetElevationMap(MapResolution))
        {
            var(winterTemperatureMap, summerTemperatureMap) = planet.GetTemperatureMaps(elevationMap, MapResolution);
            var(precipitationMaps, snowfallMaps)            = planet
                                                              .GetPrecipitationAndSnowfallMaps(winterTemperatureMap, summerTemperatureMap, MapResolution, Seasons);
            for (var i = 0; i < snowfallMaps.Length; i++)
            {
                snowfallMaps[i].Dispose();
            }
            using var precipitationMap = SurfaceMapImage.AverageImages(precipitationMaps);
            for (var i = 0; i < precipitationMaps.Length; i++)
            {
                precipitationMaps[i].Dispose();
            }
            _ = new WeatherMaps(
                planet,
                elevationMap,
                winterTemperatureMap,
                summerTemperatureMap,
                precipitationMap,
                MapResolution,
                MapProjectionOptions.Default);
            winterTemperatureMap.Dispose();
            summerTemperatureMap.Dispose();
        }

        stopwatch.Stop();

        Console.WriteLine($"Equirectangular surface map generation time: {stopwatch.Elapsed:s'.'FFF} s");

        var projection = new MapProjectionOptions(equalArea: true);

        stopwatch.Restart();

        using var elevationMapEA = planet.GetElevationMap(MapResolution, projection);
        var(winterTemperatureMapEA, summerTemperatureMapEA) = planet.GetTemperatureMaps(elevationMapEA, MapResolution, projection, projection);
        using var temperatureMapEA = SurfaceMapImage.AverageImages(winterTemperatureMapEA, summerTemperatureMapEA);
        var(precipitationMapsEA, snowfallMapsEA) = planet
                                                   .GetPrecipitationAndSnowfallMaps(winterTemperatureMapEA, summerTemperatureMapEA, MapResolution, Seasons, projection, projection);
        for (var i = 0; i < snowfallMapsEA.Length; i++)
        {
            snowfallMapsEA[i].Dispose();
        }
        using var precipitationMapEA = SurfaceMapImage.AverageImages(precipitationMapsEA);
        for (var i = 0; i < precipitationMapsEA.Length; i++)
        {
            precipitationMapsEA[i].Dispose();
        }
        var climateMapsEA = new WeatherMaps(
            planet,
            elevationMapEA,
            winterTemperatureMapEA,
            summerTemperatureMapEA,
            precipitationMapEA,
            MapResolution,
            projection);

        winterTemperatureMapEA.Dispose();
        summerTemperatureMapEA.Dispose();

        stopwatch.Stop();

        Console.WriteLine($"Cylindrical equal-area surface map generation time: {stopwatch.Elapsed:s'.'FFF} s");

        var normalizedSeaLevel = planet.SeaLevel / planet.MaxElevation;
        var elevationRange     = planet.GetElevationRange(elevationMapEA);
        var landCoords         = 0;

        if (planet.Hydrosphere?.IsEmpty == false)
        {
            for (var x = 0; x < elevationMapEA.Width; x++)
            {
                for (var y = 0; y < elevationMapEA.Height; y++)
                {
                    var value = (2.0 * elevationMapEA[x, y].PackedValue / ushort.MaxValue) - 1;
                    if (value - normalizedSeaLevel > 0)
                    {
                        landCoords++;
                    }
                }
            }
        }
        var sb = new StringBuilder();

        AddTempString(sb, temperatureMapEA);
        sb.AppendLine();
        AddTerrainString(sb, planet, elevationMapEA, landCoords);
        sb.AppendLine();
        AddClimateString(sb, elevationMapEA, normalizedSeaLevel, landCoords, climateMapsEA);
        sb.AppendLine();
        AddPrecipitationString(sb, planet, elevationMapEA, precipitationMapEA, normalizedSeaLevel, landCoords, climateMapsEA);
        Console.WriteLine(sb.ToString());
    }