Skip to content

Commit 39e75c4

Browse files
committed
feat: Define average and instantaneous wind speed and direction with updates
1 parent 39abd77 commit 39e75c4

File tree

5 files changed

+100
-50
lines changed

5 files changed

+100
-50
lines changed

Source/Orts.Simulation/Simulation/Physics/Train.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2116,8 +2116,8 @@ public void UpdateWindComponents()
21162116
// Wind and train direction to be converted to an angle between 0 and 360 deg.
21172117
// Calculate Wind speed and direction, and train direction
21182118
// Update the value of the Wind Speed and Direction for the train
2119-
PhysicsWindDirectionDeg = MathHelper.ToDegrees(Simulator.Weather.WindDirectionRad);
2120-
PhysicsWindSpeedMpS = Simulator.Weather.WindSpeedMpS.Length();
2119+
PhysicsWindDirectionDeg = MathHelper.ToDegrees(Simulator.Weather.WindInstantaneousDirectionRad);
2120+
PhysicsWindSpeedMpS = Simulator.Weather.WindInstantaneousSpeedMpS;
21212121
var speedMpS = Math.Abs(SpeedMpS);
21222122

21232123
// If a westerly direction (ie -ve) convert to an angle between 0 and 360

Source/Orts.Simulation/Simulation/Weather.cs

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,65 @@
2020

2121
namespace Orts.Simulation
2222
{
23-
public class Weather
23+
public static class WeatherConstants
2424
{
25-
// Rainy conditions (Glossary of Meteorology (June 2000). "Rain". American Meteorological Society. Retrieved 2010-01-15.):
26-
// Type Rate
27-
// Light <2.5mm/h
28-
// Moderate 2.5-7.3mm/h
29-
// Heavy >7.3mm/h
30-
// Violent >50.0mm/h
31-
//
32-
// Snowy conditions (Glossary of Meteorology (2009). "Snow". American Meteorological Society. Retrieved 2009-06-28.):
33-
// Type Visibility
34-
// Light >1.0km
35-
// Moderate 0.5-1.0km
36-
// Heavy <0.5km
25+
// Source: http://www.icscc.org.cn/upload/file/20190102/Doc.9837-EN%20Manual%20on%20Automatic%20Meteorological%20Observing%20Systems%20at%20Aerodromes.pdf
26+
// Manual on Automatic Meteorological Observing Systems at Aerodromes, Second Edition - 2011, International Civil Aviation Organization
27+
// Type Intensity Rate (mm/h)
28+
// Drizzle Light <0.1
29+
// Moderate 0.1-0.5
30+
// Heavy >0.5
31+
// Rain Light <2.5
32+
// Moderate 2.5-10.0
33+
// Heavy >10.0
34+
// Snow Light <1.0
35+
// Moderate 1.0-5.0
36+
// Heavy >5.0
37+
// Other interesting items:
38+
// - Page 79, Table A-1. MOR limit above which visibility is equal to MOR
39+
// Night/day background luminance values
40+
// - Page 81, Figure A-2. Example of diagram (T_air, RH) used for determining the present weather phenomenon
41+
// Chart of precipitation type vs air temperature and humidity
42+
43+
// Final values are twice the heavy value, as a reasonable maximum possible value for interpolation
44+
public static readonly float[] DrizzleRateMMpH = new[] { 0.0f, 0.1f, 0.5f, 1.0f };
45+
public static readonly float[] RainRateMMpH = new[] { 0.0f, 2.5f, 10.0f, 20.0f };
46+
public static readonly float[] SnowRateMMpH = new[] { 0.0f, 1.0f, 5.0f, 10.0f };
47+
48+
// Wind speed (Beaufort scale)
49+
// Number Description Speed (m/s)
50+
// 0 Calm >0.0
51+
// 1 Light air >0.5
52+
// 2 Light breeze >1.6
53+
// 3 Gentle breeze >3.4
54+
// 4 Moderate breeze >5.5
55+
// 5 Fresh breeze >8.0
56+
// 6 Strong breeze >10.8
57+
// 7 High wind >13.9
58+
// 8 Gale >17.2
59+
// 9 Strong gale >20.8
60+
// 10 Storm >24.5
61+
// 11 Violent storm >28.5
62+
// 12 Hurricane-force >32.7
63+
public static readonly float[] WindSpeedBeaufortMpS = new[] { 0.0f, 0.5f, 1.6f, 3.4f, 5.5f, 8.0f, 10.8f, 13.9f, 17.2f, 20.8f, 24.5f, 28.5f, 32.7f };
3764

65+
// Wind gusts ("Rafale". Glossaire météorologique (in French). Météo-France. Retrieved 2018-11-15.):
66+
// Type Excess speed (m/s)
67+
// Light >5.1 (10-15 knots)
68+
// Moderate >7.7 (15-25 knots)
69+
// Heavy >12.9 (>25 knots)
70+
public static readonly float[] WindSpeedGustMpS = new[] { 5.1f, 7.7f, 12.9f, 25.8f };
71+
72+
public enum Condition
73+
{
74+
Light,
75+
Moderate,
76+
Heavy,
77+
}
78+
}
79+
80+
public class Weather
81+
{
3882
// Fog/visibility distance. Ranges from 10m (can't see anything), 5km (medium), 20km (clear) to 100km (clear arctic).
3983
public float VisibilityM;
4084

@@ -47,8 +91,22 @@ public class Weather
4791
// Precipitation liquidity; 1 = rain, 0 = snow; intermediate values possible with dynamic weather.
4892
public float PrecipitationLiquidity;
4993

50-
public Vector2 WindSpeedMpS;
94+
// Wind has an average direction (normalized vector pointing WITH wind movement) and speed, and instantaneous direction and speed (e.g. gusts).
95+
public Vector2 WindAverageDirection;
96+
public Vector2 WindInstantaneousDirection;
97+
public float WindAverageSpeedMpS;
98+
public float WindInstantaneousSpeedMpS;
99+
100+
public float WindAverageDirectionRad
101+
{
102+
get => (float)Math.Atan2(WindAverageDirection.X, -WindAverageDirection.Y);
103+
set => WindAverageDirection = new Vector2((float)Math.Sin(value), -(float)Math.Cos(value));
104+
}
51105

52-
public float WindDirectionRad => (float)Math.Atan2(WindSpeedMpS.X, WindSpeedMpS.Y);
106+
public float WindInstantaneousDirectionRad
107+
{
108+
get => (float)Math.Atan2(WindInstantaneousDirection.X, -WindInstantaneousDirection.Y);
109+
set => WindInstantaneousDirection = new Vector2((float)Math.Sin(value), -(float)Math.Cos(value));
110+
}
53111
}
54112
}

Source/RunActivity/Viewer3D/ParticleEmitter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ int GetCountFreeParticles()
321321

322322
public void Update(float currentTime, ElapsedTime elapsedTime)
323323
{
324-
windDisplacementX = viewer.Simulator.Weather.WindSpeedMpS.X * 0.25f;
325-
windDisplacementZ = viewer.Simulator.Weather.WindSpeedMpS.Y * 0.25f;
324+
windDisplacementX = viewer.Simulator.Weather.WindInstantaneousDirection.X * 0.25f;
325+
windDisplacementZ = viewer.Simulator.Weather.WindInstantaneousDirection.Y * 0.25f;
326326

327327
var velocity = WorldPosition.Location - LastWorldPosition.Location;
328328
velocity.X += (WorldPosition.TileX - LastWorldPosition.TileX) * 2048;

Source/RunActivity/Viewer3D/Popups/HUDWindow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,7 @@ void TextPageWeather(TableData table)
13251325
TableAddLabelValue(table, Viewer.Catalog.GetString("Cloud cover"), Viewer.Catalog.GetStringFmt("{0:F0} %", Viewer.Simulator.Weather.CloudCoverFactor * 100));
13261326
TableAddLabelValue(table, Viewer.Catalog.GetString("Intensity"), Viewer.Catalog.GetStringFmt("{0:F4} p/s/m^2", Viewer.Simulator.Weather.PrecipitationIntensityPPSPM2));
13271327
TableAddLabelValue(table, Viewer.Catalog.GetString("Liquidity"), Viewer.Catalog.GetStringFmt("{0:F0} %", Viewer.Simulator.Weather.PrecipitationLiquidity * 100));
1328-
TableAddLabelValue(table, Viewer.Catalog.GetString("Wind"), Viewer.Catalog.GetStringFmt("{0:F1},{1:F1} m/s", Viewer.Simulator.Weather.WindSpeedMpS.X, Viewer.Simulator.Weather.WindSpeedMpS.Y));
1328+
TableAddLabelValue(table, Viewer.Catalog.GetString("Wind"), Viewer.Catalog.GetStringFmt("{0:F0} ° / {1:F1} m/s ({2:F0} ° / {3:F1} m/s gusts)", MathHelper.ToDegrees(Viewer.Simulator.Weather.WindAverageDirectionRad), Viewer.Simulator.Weather.WindAverageSpeedMpS, MathHelper.ToDegrees(Viewer.Simulator.Weather.WindInstantaneousDirectionRad), Viewer.Simulator.Weather.WindInstantaneousSpeedMpS));
13291329
// TODO: Move ambient temperature into Orts.Simulation.Weather
13301330
TableAddLabelValue(table, Viewer.Catalog.GetString("Amb Temp"), FormatStrings.FormatTemperature(Viewer.PlayerLocomotive.CarOutsideTempC, Viewer.PlayerLocomotive.IsMetric, false));
13311331
}

Source/RunActivity/Viewer3D/Weather.cs

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
namespace Orts.Viewer3D
3838
{
39+
// TODO: Probably everything in here, except sounds, should be in the simulator
3940
public class WeatherControl
4041
{
4142
public readonly Viewer Viewer;
@@ -46,20 +47,24 @@ public class WeatherControl
4647
public readonly List<SoundSourceBase> SnowSound;
4748
public readonly List<SoundSourceBase> WeatherSounds = new List<SoundSourceBase>();
4849

50+
WorldLocation CameraWorldLocation;
51+
4952
public bool weatherChangeOn = false;
5053
public DynamicWeather dynamicWeather;
5154
public bool RandomizedWeather;
5255
public bool DesertZone; // we are in a desert zone, so no randomized weather change...
5356
private float[,] DesertZones = { { 30, 45, -120, -105 } }; // minlat, maxlat, minlong, maxlong
57+
public float Time;
5458

5559
// Variables used for wind calculations
56-
Vector2 WindSpeedInternalMpS;
57-
Vector2[] windSpeedMpS = new Vector2[2];
58-
public float Time;
59-
readonly float[] WindChangeMpSS = { 40, 5 }; // Flurry, steady
60-
const float WindSpeedMaxMpS = 4.5f;
60+
const int WindSpeedBeaufort = 6;
61+
const float WindInstantaneousDirectionLimitRad = (float)(45 * Math.PI / 180);
62+
static readonly float WindInstantaneousSpeedLimit = WeatherConstants.WindSpeedGustMpS[(int)WeatherConstants.Condition.Light];
63+
const float WindNoiseScale = 10;
64+
readonly float WindInstantaneousDirectionNoiseStart = (float)Viewer.Random.NextDouble() * WindNoiseScale;
65+
readonly float WindInstantaneousSpeedNoiseStart = (float)Viewer.Random.NextDouble() * WindNoiseScale;
66+
const float WindGustUpdateTimeS = 0.25f;
6167
float WindUpdateTimer = 0.0f;
62-
float WindGustUpdateTimeS = 1.0f;
6368

6469
public WeatherControl(Viewer viewer)
6570
{
@@ -178,6 +183,9 @@ public void UpdateWeatherParameters()
178183
case Orts.Formats.Msts.WeatherType.Rain: Weather.PrecipitationLiquidity = 1; Weather.PrecipitationIntensityPPSPM2 = 0.010f; Viewer.SoundProcess.AddSoundSources(this, RainSound); break;
179184
case Orts.Formats.Msts.WeatherType.Snow: Weather.PrecipitationLiquidity = 0; Weather.PrecipitationIntensityPPSPM2 = 0.0050f; Viewer.SoundProcess.AddSoundSources(this, SnowSound); break;
180185
}
186+
187+
Weather.WindAverageDirectionRad = (float)Viewer.Random.NextDouble() * MathHelper.TwoPi;
188+
Weather.WindAverageSpeedMpS = (float)Viewer.Random.NextDouble() * WeatherConstants.WindSpeedBeaufortMpS[WindSpeedBeaufort];
181189
}
182190

183191
void UpdateSoundSources()
@@ -225,32 +233,16 @@ private void UpdateWind(ElapsedTime elapsedTime)
225233

226234
if (WindUpdateTimer > WindGustUpdateTimeS)
227235
{
228-
WindSpeedInternalMpS = Vector2.Zero;
229-
for (var i = 0; i < windSpeedMpS.Length; i++)
230-
{
231-
windSpeedMpS[i].X += (((float)Viewer.Random.NextDouble() * 2) - 1) * WindChangeMpSS[i] * WindUpdateTimer;
232-
windSpeedMpS[i].Y += (((float)Viewer.Random.NextDouble() * 2) - 1) * WindChangeMpSS[i] * WindUpdateTimer;
233-
234-
var windMagnitude = windSpeedMpS[i].Length() / (i == 0 ? Weather.WindSpeedMpS.Length() * 0.4f : WindSpeedMaxMpS);
235-
236-
if (windMagnitude > 1)
237-
{
238-
windSpeedMpS[i] /= windMagnitude;
239-
}
240-
241-
WindSpeedInternalMpS += windSpeedMpS[i];
242-
}
243-
244-
var TotalwindMagnitude = WindSpeedInternalMpS.Length() / WindSpeedMaxMpS;
236+
// Adjust instantaneous wind speed and direction
237+
var noisePos = (float)Viewer.Simulator.ClockTime / WindNoiseScale;
238+
Weather.WindInstantaneousDirectionRad = Weather.WindAverageDirectionRad + WindInstantaneousDirectionLimitRad * Noise.Generate(WindInstantaneousDirectionNoiseStart + noisePos);
239+
Weather.WindInstantaneousSpeedMpS = Math.Max(0, Weather.WindAverageSpeedMpS + WindInstantaneousSpeedLimit * Noise.Generate(WindInstantaneousSpeedNoiseStart + noisePos));
245240

246-
if (TotalwindMagnitude > 1)
247-
{
248-
WindSpeedInternalMpS /= TotalwindMagnitude;
249-
}
250-
251-
Weather.WindSpeedMpS = WindSpeedInternalMpS;
252-
WindUpdateTimer = 0.0f; // Reset wind gust timer
241+
// Reset wind gust timer
242+
WindUpdateTimer -= WindGustUpdateTimeS;
253243
}
244+
245+
CameraWorldLocation = Viewer.Camera.CameraWorldLocation;
254246
}
255247

256248
private bool RandomizeInitialWeather()

0 commit comments

Comments
 (0)