1 using System;
2 using System.Linq;
3 using LibRender2;
4 using LibRender2.Camera;
5 using LibRender2.Cameras;
6 using LibRender2.Trains;
7 using OpenBveApi.Graphics;
8 using OpenBveApi.Math;
9 using OpenBveApi.Objects;
10 using OpenBveApi.Routes;
11 using OpenBveApi.Runtime;
12 using OpenBveApi.Trains;
13 using OpenBveApi.World;
14 using SoundManager;
15 using TrainManager.BrakeSystems;
16 using TrainManager.Motor;
17 using TrainManager.Power;
18 using TrainManager.Trains;
19 
20 namespace TrainManager.Car
21 {
22 	/*
23 	 * TEMPORARY NAME AND CLASS TO ALLOW FOR MOVE IN PARTS
24 	 */
25 	public class CarBase : AbstractCar
26 	{
27 		/// <summary>A reference to the base train</summary>
28 		public TrainBase baseTrain;
29 		/// <summary>The front bogie</summary>
30 		public Bogie FrontBogie;
31 		/// <summary>The rear bogie</summary>
32 		public Bogie RearBogie;
33 		/// <summary>The doors for this car</summary>
34 		public Door[] Doors;
35 		/// <summary>The horns attached to this car</summary>
36 		public Horn[] Horns;
37 		/// <summary>Contains the physics properties for the car</summary>
38 		public CarPhysics Specs;
39 		/// <summary>The car brake for this car</summary>
40 		public CarBrake CarBrake;
41 		/// <summary>The car sections (objects) attached to the car</summary>
42 		public CarSection[] CarSections;
43 		/// <summary>The index of the current car section</summary>
44 		public int CurrentCarSection;
45 		/// <summary>The driver's eye position within the car</summary>
46 		public Vector3 Driver;
47 		/// <summary>The current yaw of the driver's eyes</summary>
48 		public double DriverYaw;
49 		/// <summary>The current pitch of the driver's eyes</summary>
50 		public double DriverPitch;
51 		/// <summary>Whether currently visible from the in-game camera location</summary>
52 		public bool CurrentlyVisible;
53 		/// <summary>Whether currently derailed</summary>
54 		public bool Derailed;
55 		/// <summary>Whether currently toppled over</summary>
56 		public bool Topples;
57 		/// <summary>The coupler between cars</summary>
58 		public Coupler Coupler;
59 		/// <summary>The breaker</summary>
60 		public Breaker Breaker;
61 		/// <summary>The windscreen</summary>
62 		public Windscreen Windscreen;
63 		/// <summary>The hold brake for this car</summary>
64 		public CarHoldBrake HoldBrake;
65 		/// <summary>The constant speed device for this car</summary>
66 		public CarConstSpeed ConstSpeed;
67 		/// <summary>The readhesion device for this car</summary>
68 		public CarReAdhesionDevice ReAdhesionDevice;
69 		/// <summary>The position of the beacon reciever within the car</summary>
70 		public double BeaconReceiverPosition;
71 		/// <summary>The beacon reciever</summary>
72 		public TrackFollower BeaconReceiver;
73 		/// <summary>Stores the camera restriction mode for the interior view of this car</summary>
74 		public CameraRestrictionMode CameraRestrictionMode = CameraRestrictionMode.NotSpecified;
75 		/// <summary>The current camera restriction mode for this car</summary>
76 		public CameraRestriction CameraRestriction;
77 		/// <summary>Stores the camera interior camera alignment for this car</summary>
78 		public CameraAlignment InteriorCamera;
79 		/// <summary>Whether loading sway is enabled for this car</summary>
80 		public bool EnableLoadingSway = true;
81 		/// <summary>Whether this car has an interior view</summary>
82 		public bool HasInteriorView = false;
83 		/// <summary>Contains the generic sounds attached to the car</summary>
84 		public CarSounds Sounds;
85 
CarBase(TrainBase train, int index, double CoefficientOfFriction, double CoefficientOfRollingResistance, double AerodynamicDragCoefficient)86 		public CarBase(TrainBase train, int index, double CoefficientOfFriction, double CoefficientOfRollingResistance, double AerodynamicDragCoefficient)
87 		{
88 			Specs = new CarPhysics();
89 			Brightness = new Brightness(this);
90 			baseTrain = train;
91 			Index = index;
92 			CarSections = new CarSection[] { };
93 			FrontAxle = new Axle(TrainManagerBase.currentHost, train, this, CoefficientOfFriction, CoefficientOfRollingResistance, AerodynamicDragCoefficient);
94 			FrontAxle.Follower.TriggerType = index == 0 ? EventTriggerType.FrontCarFrontAxle : EventTriggerType.OtherCarFrontAxle;
95 			RearAxle = new Axle(TrainManagerBase.currentHost, train, this, CoefficientOfFriction, CoefficientOfRollingResistance, AerodynamicDragCoefficient);
96 			RearAxle.Follower.TriggerType = index == baseTrain.Cars.Length - 1 ? EventTriggerType.RearCarRearAxle : EventTriggerType.OtherCarRearAxle;
97 			BeaconReceiver = new TrackFollower(TrainManagerBase.currentHost, train);
98 			FrontBogie = new Bogie(train, this, false);
99 			RearBogie = new Bogie(train, this, true);
100 			Doors = new Door[2];
101 			Horns = new[]
102 			{
103 				new Horn(this),
104 				new Horn(this),
105 				new Horn(this)
106 			};
107 			Sounds = new CarSounds();
108 			CurrentCarSection = -1;
109 			ChangeCarSection(CarSectionType.NotVisible);
110 			FrontBogie.ChangeSection(-1);
111 			RearBogie.ChangeSection(-1);
112 		}
113 
CarBase(TrainBase train, int index)114 		public CarBase(TrainBase train, int index)
115 		{
116 			baseTrain = train;
117 			Index = index;
118 			CarSections = new CarSection[] { };
119 			FrontAxle = new Axle(TrainManagerBase.currentHost, train, this);
120 			RearAxle = new Axle(TrainManagerBase.currentHost, train, this);
121 			BeaconReceiver = new TrackFollower(TrainManagerBase.currentHost, train);
122 			FrontBogie = new Bogie(train, this, false);
123 			RearBogie = new Bogie(train, this, true);
124 			Doors = new Door[2];
125 			Horns = new[]
126 			{
127 				new Horn(this),
128 				new Horn(this),
129 				new Horn(this)
130 			};
131 			Brightness = new Brightness(this);
132 		}
133 
134 		/// <summary>Moves the car</summary>
135 		/// <param name="Delta">The delta to move</param>
Move(double Delta)136 		public void Move(double Delta)
137 		{
138 			if (baseTrain.State != TrainState.Disposed)
139 			{
140 				FrontAxle.Follower.UpdateRelative(Delta, true, true);
141 				FrontBogie.FrontAxle.Follower.UpdateRelative(Delta, true, true);
142 				FrontBogie.RearAxle.Follower.UpdateRelative(Delta, true, true);
143 				if (baseTrain.State != TrainState.Disposed)
144 				{
145 					RearAxle.Follower.UpdateRelative(Delta, true, true);
146 					RearBogie.FrontAxle.Follower.UpdateRelative(Delta, true, true);
147 					RearBogie.RearAxle.Follower.UpdateRelative(Delta, true, true);
148 					if (baseTrain.State != TrainState.Disposed)
149 					{
150 						BeaconReceiver.UpdateRelative(Delta, true, true);
151 					}
152 				}
153 			}
154 		}
155 
156 		/// <summary>Call this method to update all track followers attached to the car</summary>
157 		/// <param name="NewTrackPosition">The track position change</param>
158 		/// <param name="UpdateWorldCoordinates">Whether to update the world co-ordinates</param>
159 		/// <param name="AddTrackInaccurary">Whether to add track innaccuarcy</param>
UpdateTrackFollowers(double NewTrackPosition, bool UpdateWorldCoordinates, bool AddTrackInaccurary)160 		public void UpdateTrackFollowers(double NewTrackPosition, bool UpdateWorldCoordinates, bool AddTrackInaccurary)
161 		{
162 			//Car axles
163 			FrontAxle.Follower.UpdateRelative(NewTrackPosition, UpdateWorldCoordinates, AddTrackInaccurary);
164 			RearAxle.Follower.UpdateRelative(NewTrackPosition, UpdateWorldCoordinates, AddTrackInaccurary);
165 			//Front bogie axles
166 			FrontBogie.FrontAxle.Follower.UpdateRelative(NewTrackPosition, UpdateWorldCoordinates, AddTrackInaccurary);
167 			FrontBogie.RearAxle.Follower.UpdateRelative(NewTrackPosition, UpdateWorldCoordinates, AddTrackInaccurary);
168 			//Rear bogie axles
169 
170 			RearBogie.FrontAxle.Follower.UpdateRelative(NewTrackPosition, UpdateWorldCoordinates, AddTrackInaccurary);
171 			RearBogie.RearAxle.Follower.UpdateRelative(NewTrackPosition, UpdateWorldCoordinates, AddTrackInaccurary);
172 		}
173 
174 		/// <summary>Initializes the car</summary>
Initialize()175 		public void Initialize()
176 		{
177 			for (int i = 0; i < CarSections.Length; i++)
178 			{
179 				CarSections[i].Initialize(false);
180 			}
181 
182 			for (int i = 0; i < FrontBogie.CarSections.Length; i++)
183 			{
184 				FrontBogie.CarSections[i].Initialize(false);
185 			}
186 
187 			for (int i = 0; i < RearBogie.CarSections.Length; i++)
188 			{
189 				RearBogie.CarSections[i].Initialize(false);
190 			}
191 
192 			Brightness.PreviousBrightness = 1.0f;
193 			Brightness.NextBrightness = 1.0f;
194 		}
195 
196 		/// <summary>Synchronizes the car after a period of infrequent updates</summary>
Syncronize()197 		public void Syncronize()
198 		{
199 			double s = 0.5 * (FrontAxle.Follower.TrackPosition + RearAxle.Follower.TrackPosition);
200 			double d = 0.5 * (FrontAxle.Follower.TrackPosition - RearAxle.Follower.TrackPosition);
201 			FrontAxle.Follower.UpdateAbsolute(s + d, false, false);
202 			RearAxle.Follower.UpdateAbsolute(s - d, false, false);
203 			double b = FrontAxle.Follower.TrackPosition - FrontAxle.Position + BeaconReceiverPosition;
204 			BeaconReceiver.UpdateAbsolute(b, false, false);
205 		}
206 
CreateWorldCoordinates(Vector3 Car, out Vector3 Position, out Vector3 Direction)207 		public override void CreateWorldCoordinates(Vector3 Car, out Vector3 Position, out Vector3 Direction)
208 		{
209 			Direction = FrontAxle.Follower.WorldPosition - RearAxle.Follower.WorldPosition;
210 			double t = Direction.NormSquared();
211 			if (t != 0.0)
212 			{
213 				t = 1.0 / Math.Sqrt(t);
214 				Direction *= t;
215 				double sx = Direction.Z * Up.Y - Direction.Y * Up.Z;
216 				double sy = Direction.X * Up.Z - Direction.Z * Up.X;
217 				double sz = Direction.Y * Up.X - Direction.X * Up.Y;
218 				double rx = 0.5 * (FrontAxle.Follower.WorldPosition.X + RearAxle.Follower.WorldPosition.X);
219 				double ry = 0.5 * (FrontAxle.Follower.WorldPosition.Y + RearAxle.Follower.WorldPosition.Y);
220 				double rz = 0.5 * (FrontAxle.Follower.WorldPosition.Z + RearAxle.Follower.WorldPosition.Z);
221 				Position.X = rx + sx * Car.X + Up.X * Car.Y + Direction.X * Car.Z;
222 				Position.Y = ry + sy * Car.X + Up.Y * Car.Y + Direction.Y * Car.Z;
223 				Position.Z = rz + sz * Car.X + Up.Z * Car.Y + Direction.Z * Car.Z;
224 			}
225 			else
226 			{
227 				Position = FrontAxle.Follower.WorldPosition;
228 				Direction = Vector3.Down;
229 			}
230 		}
231 
232 		public override double TrackPosition => FrontAxle.Follower.TrackPosition;
233 
234 		/// <summary>Backing property for the index of the car within the train</summary>
235 		public override int Index
236 		{
237 			get;
238 		}
239 
Reverse()240 		public override void Reverse()
241 		{
242 			// reverse axle positions
243 			double temp = FrontAxle.Position;
244 			FrontAxle.Position = -RearAxle.Position;
245 			RearAxle.Position = -temp;
246 			int idxToReverse = HasInteriorView ? 1 : 0;
247 			if (CarSections != null && CarSections.Length > 0)
248 			{
249 				foreach (AnimatedObject animatedObject in CarSections[idxToReverse].Groups[0].Elements)
250 				{
251 					animatedObject.Reverse();
252 				}
253 			}
254 
255 			Bogie b = RearBogie;
256 			RearBogie = FrontBogie;
257 			FrontBogie = b;
258 			FrontBogie.Reverse();
259 			RearBogie.Reverse();
260 			FrontBogie.FrontAxle.Follower.UpdateAbsolute(FrontAxle.Position + FrontBogie.FrontAxle.Position, true, false);
261 			FrontBogie.RearAxle.Follower.UpdateAbsolute(FrontAxle.Position + FrontBogie.RearAxle.Position, true, false);
262 
263 			RearBogie.FrontAxle.Follower.UpdateAbsolute(RearAxle.Position + RearBogie.FrontAxle.Position, true, false);
264 			RearBogie.RearAxle.Follower.UpdateAbsolute(RearAxle.Position + RearBogie.RearAxle.Position, true, false);
265 
266 		}
267 
OpenDoors(bool Left, bool Right)268 		public override void OpenDoors(bool Left, bool Right)
269 		{
270 			bool sl = false, sr = false;
271 			if (Left & !Doors[0].AnticipatedOpen & (baseTrain.SafetySystems.DoorInterlockState == DoorInterlockStates.Left | baseTrain.SafetySystems.DoorInterlockState == DoorInterlockStates.Unlocked))
272 			{
273 				Doors[0].AnticipatedOpen = true;
274 				sl = true;
275 			}
276 
277 			if (Right & !Doors[1].AnticipatedOpen & (baseTrain.SafetySystems.DoorInterlockState == DoorInterlockStates.Right | baseTrain.SafetySystems.DoorInterlockState == DoorInterlockStates.Unlocked))
278 			{
279 				Doors[1].AnticipatedOpen = true;
280 				sr = true;
281 			}
282 
283 			if (sl)
284 			{
285 				Doors[0].OpenSound.Play(Specs.DoorOpenPitch, 1.0, this, false);
286 				for (int i = 0; i < Doors.Length; i++)
287 				{
288 					if (Doors[i].Direction == -1)
289 					{
290 						Doors[i].DoorLockDuration = 0.0;
291 					}
292 				}
293 			}
294 
295 			if (sr)
296 			{
297 				Doors[1].OpenSound.Play(Specs.DoorOpenPitch, 1.0, this, false);
298 				for (int i = 0; i < Doors.Length; i++)
299 				{
300 					if (Doors[i].Direction == 1)
301 					{
302 						Doors[i].DoorLockDuration = 0.0;
303 					}
304 				}
305 			}
306 
307 			for (int i = 0; i < Doors.Length; i++)
308 			{
309 				if (Doors[i].AnticipatedOpen)
310 				{
311 					Doors[i].NextReopenTime = 0.0;
312 					Doors[i].ReopenCounter++;
313 				}
314 			}
315 		}
316 
317 		/// <summary>Returns the combination of door states what encountered at the specified car in a train.</summary>
318 		/// <param name="Left">Whether to include left doors.</param>
319 		/// <param name="Right">Whether to include right doors.</param>
320 		/// <returns>A bit mask combining encountered door states.</returns>
GetDoorsState(bool Left, bool Right)321 		public TrainDoorState GetDoorsState(bool Left, bool Right)
322 		{
323 			bool opened = false, closed = false, mixed = false;
324 			for (int i = 0; i < Doors.Length; i++)
325 			{
326 				if (Left & Doors[i].Direction == -1 | Right & Doors[i].Direction == 1)
327 				{
328 					if (Doors[i].State == 0.0)
329 					{
330 						closed = true;
331 					}
332 					else if (Doors[i].State == 1.0)
333 					{
334 						opened = true;
335 					}
336 					else
337 					{
338 						mixed = true;
339 					}
340 				}
341 			}
342 
343 			TrainDoorState Result = TrainDoorState.None;
344 			if (opened) Result |= TrainDoorState.Opened;
345 			if (closed) Result |= TrainDoorState.Closed;
346 			if (mixed) Result |= TrainDoorState.Mixed;
347 			if (opened & !closed & !mixed) Result |= TrainDoorState.AllOpened;
348 			if (!opened & closed & !mixed) Result |= TrainDoorState.AllClosed;
349 			if (!opened & !closed & mixed) Result |= TrainDoorState.AllMixed;
350 			return Result;
351 		}
352 
UpdateRunSounds(double TimeElapsed)353 		public void UpdateRunSounds(double TimeElapsed)
354 		{
355 			if (Sounds.Run == null || Sounds.Run.Count == 0)
356 			{
357 				return;
358 			}
359 
360 			const double factor = 0.04; // 90 km/h -> m/s -> 1/x
361 			double speed = Math.Abs(CurrentSpeed);
362 			if (Derailed)
363 			{
364 				speed = 0.0;
365 			}
366 
367 			double pitch = speed * factor;
368 			double basegain;
369 			if (CurrentSpeed == 0.0)
370 			{
371 				if (Index != 0)
372 				{
373 					Sounds.RunNextReasynchronizationPosition = baseTrain.Cars[0].FrontAxle.Follower.TrackPosition;
374 				}
375 			}
376 			else if (Sounds.RunNextReasynchronizationPosition == double.MaxValue & FrontAxle.RunIndex >= 0)
377 			{
378 				double distance = Math.Abs(FrontAxle.Follower.TrackPosition - TrainManagerBase.Renderer.CameraTrackFollower.TrackPosition);
379 				const double minDistance = 150.0;
380 				const double maxDistance = 750.0;
381 				if (distance > minDistance)
382 				{
383 					if (Sounds.Run.ContainsKey(FrontAxle.RunIndex))
384 					{
385 						SoundBuffer buffer = Sounds.Run[FrontAxle.RunIndex].Buffer;
386 						if (buffer != null)
387 						{
388 							if (buffer.Duration > 0.0)
389 							{
390 								double offset = distance > maxDistance ? 25.0 : 300.0;
391 								Sounds.RunNextReasynchronizationPosition = buffer.Duration * Math.Ceiling((baseTrain.Cars[0].FrontAxle.Follower.TrackPosition + offset) / buffer.Duration);
392 							}
393 						}
394 					}
395 				}
396 			}
397 
398 			if (FrontAxle.Follower.TrackPosition >= Sounds.RunNextReasynchronizationPosition)
399 			{
400 				Sounds.RunNextReasynchronizationPosition = double.MaxValue;
401 				basegain = 0.0;
402 			}
403 			else
404 			{
405 				basegain = speed < 2.77777777777778 ? 0.36 * speed : 1.0;
406 			}
407 
408 			for (int j = 0; j < Sounds.Run.Count; j++)
409 			{
410 				int key = Sounds.Run.ElementAt(j).Key;
411 				if (key == FrontAxle.RunIndex | key == RearAxle.RunIndex)
412 				{
413 					Sounds.Run[key].TargetVolume += 3.0 * TimeElapsed;
414 					if (Sounds.Run[key].TargetVolume > 1.0) Sounds.Run[key].TargetVolume = 1.0;
415 				}
416 				else
417 				{
418 					Sounds.Run[key].TargetVolume -= 3.0 * TimeElapsed;
419 					if (Sounds.Run[key].TargetVolume < 0.0) Sounds.Run[key].TargetVolume = 0.0;
420 				}
421 
422 				double gain = basegain * Sounds.Run[key].TargetVolume;
423 				if (Sounds.Run[key].IsPlaying)
424 				{
425 					if (pitch > 0.01 & gain > 0.001)
426 					{
427 						Sounds.Run[key].Source.Pitch = pitch;
428 						Sounds.Run[key].Source.Volume = gain;
429 					}
430 					else
431 					{
432 						TrainManagerBase.currentHost.StopSound(Sounds.Run[key].Source);
433 					}
434 				}
435 				else if (pitch > 0.02 & gain > 0.01)
436 				{
437 					Sounds.Run[key].Play(pitch, gain, this, true);
438 				}
439 			}
440 		}
441 
UpdateMotorSounds(double TimeElapsed)442 		public void UpdateMotorSounds(double TimeElapsed)
443 		{
444 			if (!this.Specs.IsMotorCar)
445 			{
446 				return;
447 			}
448 
449 			double speed = Math.Abs(Specs.PerceivedSpeed);
450 			int idx = (int) Math.Round(speed * Sounds.Motor.SpeedConversionFactor);
451 			int odir = Sounds.Motor.CurrentAccelerationDirection;
452 			int ndir = Math.Sign(Specs.MotorAcceleration);
453 			for (int h = 0; h < 2; h++)
454 			{
455 				int j = h == 0 ? BVEMotorSound.MotorP1 : BVEMotorSound.MotorP2;
456 				int k = h == 0 ? BVEMotorSound.MotorB1 : BVEMotorSound.MotorB2;
457 				if (odir > 0 & ndir <= 0)
458 				{
459 					if (j < Sounds.Motor.Tables.Length)
460 					{
461 						TrainManagerBase.currentHost.StopSound(Sounds.Motor.Tables[j].Source);
462 						Sounds.Motor.Tables[j].Source = null;
463 						Sounds.Motor.Tables[j].Buffer = null;
464 					}
465 				}
466 				else if (odir < 0 & ndir >= 0)
467 				{
468 					if (k < Sounds.Motor.Tables.Length)
469 					{
470 						TrainManagerBase.currentHost.StopSound(Sounds.Motor.Tables[k].Source);
471 						Sounds.Motor.Tables[k].Source = null;
472 						Sounds.Motor.Tables[k].Buffer = null;
473 					}
474 				}
475 
476 				if (ndir != 0)
477 				{
478 					if (ndir < 0) j = k;
479 					if (j < Sounds.Motor.Tables.Length)
480 					{
481 						int idx2 = idx;
482 						if (idx2 >= Sounds.Motor.Tables[j].Entries.Length)
483 						{
484 							idx2 = Sounds.Motor.Tables[j].Entries.Length - 1;
485 						}
486 
487 						if (idx2 >= 0)
488 						{
489 							SoundBuffer obuf = Sounds.Motor.Tables[j].Buffer;
490 							SoundBuffer nbuf = Sounds.Motor.Tables[j].Entries[idx2].Buffer;
491 							double pitch = Sounds.Motor.Tables[j].Entries[idx2].Pitch;
492 							double gain = Sounds.Motor.Tables[j].Entries[idx2].Gain;
493 							if (ndir == 1)
494 							{
495 								// power
496 								double max = Specs.AccelerationCurveMaximum;
497 								if (max != 0.0)
498 								{
499 									double cur = Specs.MotorAcceleration;
500 									if (cur < 0.0) cur = 0.0;
501 									gain *= Math.Pow(cur / max, 0.25);
502 								}
503 							}
504 							else if (ndir == -1)
505 							{
506 								// brake
507 								double max = CarBrake.DecelerationAtServiceMaximumPressure(baseTrain.Handles.Brake.Actual, CurrentSpeed);
508 								if (max != 0.0)
509 								{
510 									double cur = -Specs.MotorAcceleration;
511 									if (cur < 0.0) cur = 0.0;
512 									gain *= Math.Pow(cur / max, 0.25);
513 								}
514 							}
515 
516 							if (obuf != nbuf)
517 							{
518 								TrainManagerBase.currentHost.StopSound(Sounds.Motor.Tables[j].Source);
519 								if (nbuf != null)
520 								{
521 									Sounds.Motor.Tables[j].Source = (SoundSource) TrainManagerBase.currentHost.PlaySound(nbuf, pitch, gain, Sounds.Motor.Position, this, true);
522 									Sounds.Motor.Tables[j].Buffer = nbuf;
523 								}
524 								else
525 								{
526 									Sounds.Motor.Tables[j].Source = null;
527 									Sounds.Motor.Tables[j].Buffer = null;
528 								}
529 							}
530 							else if (nbuf != null)
531 							{
532 								if (Sounds.Motor.Tables[j].Source != null)
533 								{
534 									Sounds.Motor.Tables[j].Source.Pitch = pitch;
535 									Sounds.Motor.Tables[j].Source.Volume = gain;
536 								}
537 							}
538 							else
539 							{
540 								TrainManagerBase.currentHost.StopSound(Sounds.Motor.Tables[j].Source);
541 								Sounds.Motor.Tables[j].Source = null;
542 								Sounds.Motor.Tables[j].Buffer = null;
543 							}
544 						}
545 						else
546 						{
547 							TrainManagerBase.currentHost.StopSound(Sounds.Motor.Tables[j].Source);
548 							Sounds.Motor.Tables[j].Source = null;
549 							Sounds.Motor.Tables[j].Buffer = null;
550 						}
551 					}
552 				}
553 			}
554 
555 			Sounds.Motor.CurrentAccelerationDirection = ndir;
556 		}
557 
558 		/// <summary>Loads Car Sections (Exterior objects etc.) for this car</summary>
559 		/// <param name="currentObject">The object to add to the car sections array</param>
560 		/// <param name="visibleFromInterior">Wether this is visible from the interior of other cars</param>
LoadCarSections(UnifiedObject currentObject, bool visibleFromInterior)561 		public void LoadCarSections(UnifiedObject currentObject, bool visibleFromInterior)
562 		{
563 			int j = CarSections.Length;
564 			Array.Resize(ref CarSections, j + 1);
565 			CarSections[j] = new CarSection(TrainManagerBase.currentHost, ObjectType.Dynamic, visibleFromInterior, currentObject);
566 		}
567 
568 		/// <summary>Changes the currently visible car section</summary>
569 		/// <param name="newCarSection">The type of new car section to display</param>
570 		/// <param name="trainVisible">Whether the train is visible</param>
ChangeCarSection(CarSectionType newCarSection, bool trainVisible = false)571 		public void ChangeCarSection(CarSectionType newCarSection, bool trainVisible = false)
572 		{
573 			if(CurrentCarSection == (int)newCarSection)
574 			{
575 				return;
576 			}
577 			if (trainVisible)
578 			{
579 				if (CurrentCarSection != -1 && CarSections[CurrentCarSection].VisibleFromInterior)
580 				{
581 					return;
582 				}
583 			}
584 
585 			for (int i = 0; i < CarSections.Length; i++)
586 			{
587 				for (int j = 0; j < CarSections[i].Groups.Length; j++)
588 				{
589 					for (int k = 0; k < CarSections[i].Groups[j].Elements.Length; k++)
590 					{
591 						TrainManagerBase.currentHost.HideObject(CarSections[i].Groups[j].Elements[k].internalObject);
592 					}
593 				}
594 			}
595 
596 			switch (newCarSection)
597 			{
598 				case CarSectionType.NotVisible:
599 					this.CurrentCarSection = -1;
600 					break;
601 				case CarSectionType.Interior:
602 					if (this.HasInteriorView && this.CarSections.Length > 0)
603 					{
604 						this.CurrentCarSection = 0;
605 						this.CarSections[0].Initialize(false);
606 						CarSections[0].Show();
607 						break;
608 					}
609 
610 					this.CurrentCarSection = -1;
611 					break;
612 				case CarSectionType.Exterior:
613 					if (this.HasInteriorView && this.CarSections.Length > 1)
614 					{
615 						this.CurrentCarSection = 1;
616 						this.CarSections[1].Initialize(false);
617 						CarSections[1].Show();
618 						break;
619 					}
620 					else if (!this.HasInteriorView && this.CarSections.Length > 0)
621 					{
622 						this.CurrentCarSection = 0;
623 						this.CarSections[0].Initialize(false);
624 						CarSections[0].Show();
625 						break;
626 					}
627 
628 					this.CurrentCarSection = -1;
629 					break;
630 			}
631 
632 			//When changing car section, do not apply damping
633 			//This stops objects from spinning if the last position before they were hidden is different
634 			UpdateObjects(0.0, true, false);
635 		}
636 
637 		/// <summary>Updates the currently displayed objects for this car</summary>
638 		/// <param name="TimeElapsed">The time elapsed</param>
639 		/// <param name="ForceUpdate">Whether this is a forced update</param>
640 		/// <param name="EnableDamping">Whether damping is applied during this update (Skipped on transitions between camera views etc.)</param>
UpdateObjects(double TimeElapsed, bool ForceUpdate, bool EnableDamping)641 		public void UpdateObjects(double TimeElapsed, bool ForceUpdate, bool EnableDamping)
642 		{
643 			// calculate positions and directions for section element update
644 
645 			Vector3 d = new Vector3(FrontAxle.Follower.WorldPosition - RearAxle.Follower.WorldPosition);
646 			Vector3 s;
647 			double t = d.NormSquared();
648 			if (t != 0.0)
649 			{
650 				t = 1.0 / Math.Sqrt(t);
651 				d *= t;
652 				s.X = d.Z * Up.Y - d.Y * Up.Z;
653 				s.Y = d.X * Up.Z - d.Z * Up.X;
654 				s.Z = d.Y * Up.X - d.X * Up.Y;
655 			}
656 			else
657 			{
658 				s = Vector3.Right;
659 			}
660 
661 			Vector3 p = new Vector3(0.5 * (FrontAxle.Follower.WorldPosition + RearAxle.Follower.WorldPosition));
662 			p -= d * (0.5 * (FrontAxle.Position + RearAxle.Position));
663 			// determine visibility
664 			Vector3 cd = new Vector3(p - TrainManagerBase.Renderer.Camera.AbsolutePosition);
665 			double dist = cd.NormSquared();
666 			double bid = TrainManagerBase.Renderer.Camera.ViewingDistance + Length;
667 			CurrentlyVisible = dist < bid * bid;
668 			// Updates the brightness value
669 			byte dnb = (byte)Brightness.CurrentBrightness(TrainManagerBase.Renderer.Lighting.DynamicCabBrightness, 0.0);
670 			// update current section
671 			int cs = CurrentCarSection;
672 			if (cs >= 0 && cs < CarSections.Length)
673 			{
674 				if (CarSections[cs].Groups.Length > 0)
675 				{
676 					for (int i = 0; i < CarSections[cs].Groups[0].Elements.Length; i++)
677 					{
678 						UpdateCarSectionElement(cs, 0, i, p, d, s, CurrentlyVisible, TimeElapsed, ForceUpdate, EnableDamping);
679 
680 						// brightness change
681 						if (CarSections[cs].Groups[0].Elements[i].internalObject != null)
682 						{
683 							CarSections[cs].Groups[0].Elements[i].internalObject.DaytimeNighttimeBlend = dnb;
684 						}
685 					}
686 				}
687 
688 				int add = CarSections[cs].CurrentAdditionalGroup + 1;
689 				if (add < CarSections[cs].Groups.Length)
690 				{
691 					for (int i = 0; i < CarSections[cs].Groups[add].Elements.Length; i++)
692 					{
693 						UpdateCarSectionElement(cs, add, i, p, d, s, CurrentlyVisible, TimeElapsed, ForceUpdate, EnableDamping);
694 
695 						// brightness change
696 						if (CarSections[cs].Groups[add].Elements[i].internalObject != null)
697 						{
698 							CarSections[cs].Groups[add].Elements[i].internalObject.DaytimeNighttimeBlend = dnb;
699 						}
700 					}
701 
702 					if (CarSections[cs].Groups[add].TouchElements != null)
703 					{
704 						for (int i = 0; i < CarSections[cs].Groups[add].TouchElements.Length; i++)
705 						{
706 							UpdateCarSectionTouchElement(cs, add, i, p, d, s, false, TimeElapsed, ForceUpdate, EnableDamping);
707 						}
708 					}
709 				}
710 			}
711 			//Update camera restriction
712 
713 			CameraRestriction.AbsoluteBottomLeft = new Vector3(CameraRestriction.BottomLeft);
714 			CameraRestriction.AbsoluteBottomLeft += Driver;
715 			CameraRestriction.AbsoluteBottomLeft.Rotate(new Transformation(d, Up, s));
716 			CameraRestriction.AbsoluteBottomLeft.Translate(p);
717 
718 			CameraRestriction.AbsoluteTopRight = new Vector3(CameraRestriction.TopRight);
719 			CameraRestriction.AbsoluteTopRight += Driver;
720 			CameraRestriction.AbsoluteTopRight.Rotate(new Transformation(d, Up, s));
721 			CameraRestriction.AbsoluteTopRight.Translate(p);
722 		}
723 
724 		/// <summary>Updates the given car section element</summary>
725 		/// <param name="SectionIndex">The car section</param>
726 		/// <param name="GroupIndex">The group within the car section</param>
727 		/// <param name="ElementIndex">The element within the group</param>
728 		/// <param name="Position"></param>
729 		/// <param name="Direction"></param>
730 		/// <param name="Side"></param>
731 		/// <param name="Show"></param>
732 		/// <param name="TimeElapsed"></param>
733 		/// <param name="ForceUpdate"></param>
734 		/// <param name="EnableDamping"></param>
UpdateCarSectionElement(int SectionIndex, int GroupIndex, int ElementIndex, Vector3 Position, Vector3 Direction, Vector3 Side, bool Show, double TimeElapsed, bool ForceUpdate, bool EnableDamping)735 		private void UpdateCarSectionElement(int SectionIndex, int GroupIndex, int ElementIndex, Vector3 Position, Vector3 Direction, Vector3 Side, bool Show, double TimeElapsed, bool ForceUpdate, bool EnableDamping)
736 		{
737 			Vector3 p;
738 			if (CarSections[SectionIndex].Groups[GroupIndex].Type == ObjectType.Overlay & (TrainManagerBase.Renderer.Camera.CurrentRestriction != CameraRestrictionMode.NotAvailable && TrainManagerBase.Renderer.Camera.CurrentRestriction != CameraRestrictionMode.Restricted3D))
739 			{
740 				p = new Vector3(Driver.X, Driver.Y, Driver.Z);
741 			}
742 			else
743 			{
744 				p = Position;
745 			}
746 
747 			double timeDelta;
748 			bool updatefunctions;
749 			if (CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].RefreshRate != 0.0)
750 			{
751 				if (CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].SecondsSinceLastUpdate >= CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].RefreshRate)
752 				{
753 					timeDelta = CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].SecondsSinceLastUpdate;
754 					CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].SecondsSinceLastUpdate = TimeElapsed;
755 					updatefunctions = true;
756 				}
757 				else
758 				{
759 					timeDelta = TimeElapsed;
760 					CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].SecondsSinceLastUpdate += TimeElapsed;
761 					updatefunctions = false;
762 				}
763 			}
764 			else
765 			{
766 				timeDelta = CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].SecondsSinceLastUpdate;
767 				CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].SecondsSinceLastUpdate = TimeElapsed;
768 				updatefunctions = true;
769 			}
770 
771 			if (ForceUpdate)
772 			{
773 				updatefunctions = true;
774 			}
775 
776 			CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].Update(true, baseTrain, Index, CurrentCarSection, FrontAxle.Follower.TrackPosition - FrontAxle.Position, p, Direction, Up, Side, updatefunctions, Show, timeDelta, EnableDamping, false, CarSections[SectionIndex].Groups[GroupIndex].Type == ObjectType.Overlay ? TrainManagerBase.Renderer.Camera : null);
777 			if (!TrainManagerBase.Renderer.ForceLegacyOpenGL && CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].UpdateVAO)
778 			{
779 				VAOExtensions.CreateVAO(ref CarSections[SectionIndex].Groups[GroupIndex].Elements[ElementIndex].internalObject.Prototype.Mesh, true, TrainManagerBase.Renderer.DefaultShader.VertexLayout, TrainManagerBase.Renderer);
780 			}
781 		}
782 
UpdateCarSectionTouchElement(int SectionIndex, int GroupIndex, int ElementIndex, Vector3 Position, Vector3 Direction, Vector3 Side, bool Show, double TimeElapsed, bool ForceUpdate, bool EnableDamping)783 		private void UpdateCarSectionTouchElement(int SectionIndex, int GroupIndex, int ElementIndex, Vector3 Position, Vector3 Direction, Vector3 Side, bool Show, double TimeElapsed, bool ForceUpdate, bool EnableDamping)
784 		{
785 			Vector3 p;
786 			if (CarSections[SectionIndex].Groups[GroupIndex].Type == ObjectType.Overlay & (TrainManagerBase.Renderer.Camera.CurrentRestriction != CameraRestrictionMode.NotAvailable && TrainManagerBase.Renderer.Camera.CurrentRestriction != CameraRestrictionMode.Restricted3D))
787 			{
788 				p = new Vector3(Driver.X, Driver.Y, Driver.Z);
789 			}
790 			else
791 			{
792 				p = Position;
793 			}
794 
795 			double timeDelta;
796 			bool updatefunctions;
797 			if (CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.RefreshRate != 0.0)
798 			{
799 				if (CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.SecondsSinceLastUpdate >= CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.RefreshRate)
800 				{
801 					timeDelta = CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.SecondsSinceLastUpdate;
802 					CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.SecondsSinceLastUpdate = TimeElapsed;
803 					updatefunctions = true;
804 				}
805 				else
806 				{
807 					timeDelta = TimeElapsed;
808 					CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.SecondsSinceLastUpdate += TimeElapsed;
809 					updatefunctions = false;
810 				}
811 			}
812 			else
813 			{
814 				timeDelta = CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.SecondsSinceLastUpdate;
815 				CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.SecondsSinceLastUpdate = TimeElapsed;
816 				updatefunctions = true;
817 			}
818 
819 			if (ForceUpdate)
820 			{
821 				updatefunctions = true;
822 			}
823 
824 			CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.Update(true, baseTrain, Index, CurrentCarSection, FrontAxle.Follower.TrackPosition - FrontAxle.Position, p, Direction, Up, Side, updatefunctions, Show, timeDelta, EnableDamping, true, CarSections[SectionIndex].Groups[GroupIndex].Type == ObjectType.Overlay ? TrainManagerBase.Renderer.Camera : null);
825 			if (!TrainManagerBase.Renderer.ForceLegacyOpenGL && CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.UpdateVAO)
826 			{
827 				VAOExtensions.CreateVAO(ref CarSections[SectionIndex].Groups[GroupIndex].TouchElements[ElementIndex].Element.internalObject.Prototype.Mesh, true, TrainManagerBase.Renderer.DefaultShader.VertexLayout, TrainManagerBase.Renderer);
828 			}
829 		}
830 
UpdateTopplingCantAndSpring(double TimeElapsed)831 		public void UpdateTopplingCantAndSpring(double TimeElapsed)
832 		{
833 			// get direction, up and side vectors
834 			Vector3 d = new Vector3(FrontAxle.Follower.WorldPosition - RearAxle.Follower.WorldPosition);
835 			Vector3 s;
836 			{
837 				double t = 1.0 / d.Norm();
838 				d *= t;
839 				t = 1.0 / Math.Sqrt(d.X * d.X + d.Z * d.Z);
840 				double ex = d.X * t;
841 				double ez = d.Z * t;
842 				s = new Vector3(ez, 0.0, -ex);
843 				Up = Vector3.Cross(d, s);
844 			}
845 
846 			double r = 0.0, rs = 0.0;
847 			if (FrontAxle.Follower.CurveRadius != 0.0 & RearAxle.Follower.CurveRadius != 0.0)
848 			{
849 				r = Math.Sqrt(Math.Abs(FrontAxle.Follower.CurveRadius * RearAxle.Follower.CurveRadius));
850 				rs = Math.Sign(FrontAxle.Follower.CurveRadius + RearAxle.Follower.CurveRadius);
851 			}
852 			else if (FrontAxle.Follower.CurveRadius != 0.0)
853 			{
854 				r = Math.Abs(FrontAxle.Follower.CurveRadius);
855 				rs = Math.Sign(FrontAxle.Follower.CurveRadius);
856 			}
857 			else if (RearAxle.Follower.CurveRadius != 0.0)
858 			{
859 				r = Math.Abs(RearAxle.Follower.CurveRadius);
860 				rs = Math.Sign(RearAxle.Follower.CurveRadius);
861 			}
862 
863 			// roll due to shaking
864 			{
865 
866 				double a0 = Specs.RollDueToShakingAngle;
867 				double a1 = 0.0;
868 				if (Specs.RollShakeDirection != 0.0)
869 				{
870 					const double c0 = 0.03;
871 					const double c1 = 0.15;
872 					a1 = c1 * Math.Atan(c0 * Specs.RollShakeDirection);
873 					double dr = 0.5 + Specs.RollShakeDirection * Specs.RollShakeDirection;
874 					if (Specs.RollShakeDirection < 0.0)
875 					{
876 						Specs.RollShakeDirection += dr * TimeElapsed;
877 						if (Specs.RollShakeDirection > 0.0) Specs.RollShakeDirection = 0.0;
878 					}
879 					else
880 					{
881 						Specs.RollShakeDirection -= dr * TimeElapsed;
882 						if (Specs.RollShakeDirection < 0.0) Specs.RollShakeDirection = 0.0;
883 					}
884 				}
885 
886 				double SpringAcceleration;
887 				if (!Derailed)
888 				{
889 					SpringAcceleration = 15.0 * Math.Abs(a1 - a0);
890 				}
891 				else
892 				{
893 					SpringAcceleration = 1.5 * Math.Abs(a1 - a0);
894 				}
895 
896 				double SpringDeceleration = 0.25 * SpringAcceleration;
897 				Specs.RollDueToShakingAngularSpeed += Math.Sign(a1 - a0) * SpringAcceleration * TimeElapsed;
898 				double x = Math.Sign(Specs.RollDueToShakingAngularSpeed) * SpringDeceleration * TimeElapsed;
899 				if (Math.Abs(x) < Math.Abs(Specs.RollDueToShakingAngularSpeed))
900 				{
901 					Specs.RollDueToShakingAngularSpeed -= x;
902 				}
903 				else
904 				{
905 					Specs.RollDueToShakingAngularSpeed = 0.0;
906 				}
907 
908 				a0 += Specs.RollDueToShakingAngularSpeed * TimeElapsed;
909 				Specs.RollDueToShakingAngle = a0;
910 			}
911 			// roll due to cant (incorporates shaking)
912 			{
913 				double cantAngle = Math.Atan(Math.Tan(0.5 * (Math.Atan(FrontAxle.Follower.CurveCant) + Math.Atan(RearAxle.Follower.CurveCant))) / TrainManagerBase.currentHost.Tracks[FrontAxle.Follower.TrackIndex].RailGauge);
914 				Specs.RollDueToCantAngle = cantAngle + Specs.RollDueToShakingAngle;
915 			}
916 			// pitch due to acceleration
917 			{
918 				for (int i = 0; i < 3; i++)
919 				{
920 					double a, v, j;
921 					switch (i)
922 					{
923 						case 0:
924 							a = Specs.Acceleration;
925 							v = Specs.PitchDueToAccelerationFastValue;
926 							j = 1.8;
927 							break;
928 						case 1:
929 							a = Specs.PitchDueToAccelerationFastValue;
930 							v = Specs.PitchDueToAccelerationMediumValue;
931 							j = 1.2;
932 							break;
933 						default:
934 							a = Specs.PitchDueToAccelerationFastValue;
935 							v = Specs.PitchDueToAccelerationSlowValue;
936 							j = 1.0;
937 							break;
938 					}
939 
940 					double da = a - v;
941 					if (da < 0.0)
942 					{
943 						v -= j * TimeElapsed;
944 						if (v < a) v = a;
945 					}
946 					else
947 					{
948 						v += j * TimeElapsed;
949 						if (v > a) v = a;
950 					}
951 
952 					switch (i)
953 					{
954 						case 0:
955 							Specs.PitchDueToAccelerationFastValue = v;
956 							break;
957 						case 1:
958 							Specs.PitchDueToAccelerationMediumValue = v;
959 							break;
960 						default:
961 							Specs.PitchDueToAccelerationSlowValue = v;
962 							break;
963 					}
964 				}
965 
966 				{
967 					double da = Specs.PitchDueToAccelerationSlowValue - Specs.PitchDueToAccelerationFastValue;
968 					Specs.PitchDueToAccelerationTargetAngle = 0.03 * Math.Atan(da);
969 				}
970 				{
971 					double a = 3.0 * Math.Sign(Specs.PitchDueToAccelerationTargetAngle - Specs.PitchDueToAccelerationAngle);
972 					Specs.PitchDueToAccelerationAngularSpeed += a * TimeElapsed;
973 					double ds = Math.Abs(Specs.PitchDueToAccelerationTargetAngle - Specs.PitchDueToAccelerationAngle);
974 					if (Math.Abs(Specs.PitchDueToAccelerationAngularSpeed) > ds)
975 					{
976 						Specs.PitchDueToAccelerationAngularSpeed = ds * Math.Sign(Specs.PitchDueToAccelerationAngularSpeed);
977 					}
978 
979 					Specs.PitchDueToAccelerationAngle += Specs.PitchDueToAccelerationAngularSpeed * TimeElapsed;
980 				}
981 			}
982 			// derailment
983 			if (TrainManagerBase.Derailments & !Derailed)
984 			{
985 				double a = Specs.RollDueToTopplingAngle + Specs.RollDueToCantAngle;
986 				double sa = Math.Sign(a);
987 				if (a * sa > Specs.CriticalTopplingAngle)
988 				{
989 					baseTrain.Derail(Index, TimeElapsed);
990 				}
991 			}
992 
993 			// toppling roll
994 			if (TrainManagerBase.Toppling | Derailed)
995 			{
996 				double a = Specs.RollDueToTopplingAngle;
997 				double ab = Specs.RollDueToTopplingAngle + Specs.RollDueToCantAngle;
998 				double h = Specs.CenterOfGravityHeight;
999 				double sr = Math.Abs(CurrentSpeed);
1000 				double rmax = 2.0 * h * sr * sr / (TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity * TrainManagerBase.currentHost.Tracks[FrontAxle.Follower.TrackIndex].RailGauge);
1001 				double ta;
1002 				Topples = false;
1003 				if (Derailed)
1004 				{
1005 					double sab = Math.Sign(ab);
1006 					ta = 0.5 * Math.PI * (sab == 0.0 ? TrainManagerBase.RandomNumberGenerator.NextDouble() < 0.5 ? -1.0 : 1.0 : sab);
1007 				}
1008 				else
1009 				{
1010 					if (r != 0.0)
1011 					{
1012 						if (r < rmax)
1013 						{
1014 							double s0 = Math.Sqrt(r * TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity * TrainManagerBase.currentHost.Tracks[FrontAxle.Follower.TrackIndex].RailGauge / (2.0 * h));
1015 							const double fac = 0.25; // arbitrary coefficient
1016 							ta = -fac * (sr - s0) * rs;
1017 							Topples = true;
1018 							//FIXME: DEBUG MESSAGE
1019 							//baseTrain.Topple(Index, TimeElapsed);
1020 						}
1021 						else
1022 						{
1023 							ta = 0.0;
1024 						}
1025 					}
1026 					else
1027 					{
1028 						ta = 0.0;
1029 					}
1030 				}
1031 
1032 				double td = 1.0;
1033 				if (Derailed)
1034 				{
1035 					td = Math.Abs(ab);
1036 					if (td < 0.1) td = 0.1;
1037 				}
1038 
1039 				if (a > ta)
1040 				{
1041 					double da = a - ta;
1042 					if (td > da) td = da;
1043 					a -= td * TimeElapsed;
1044 				}
1045 				else if (a < ta)
1046 				{
1047 					double da = ta - a;
1048 					if (td > da) td = da;
1049 					a += td * TimeElapsed;
1050 				}
1051 
1052 				Specs.RollDueToTopplingAngle = a;
1053 			}
1054 			else
1055 			{
1056 				Specs.RollDueToTopplingAngle = 0.0;
1057 			}
1058 
1059 			// apply position due to cant/toppling
1060 			{
1061 				double a = Specs.RollDueToTopplingAngle + Specs.RollDueToCantAngle;
1062 				double x = Math.Sign(a) * 0.5 * TrainManagerBase.currentHost.Tracks[FrontAxle.Follower.TrackIndex].RailGauge * (1.0 - Math.Cos(a));
1063 				double y = Math.Abs(0.5 * TrainManagerBase.currentHost.Tracks[FrontAxle.Follower.TrackIndex].RailGauge * Math.Sin(a));
1064 				Vector3 cc = new Vector3(s.X * x + Up.X * y, s.Y * x + Up.Y * y, s.Z * x + Up.Z * y);
1065 				FrontAxle.Follower.WorldPosition += cc;
1066 				RearAxle.Follower.WorldPosition += cc;
1067 			}
1068 			// apply rolling
1069 			{
1070 				s.Rotate(d, -Specs.RollDueToTopplingAngle - Specs.RollDueToCantAngle);
1071 				Up.Rotate(d, -Specs.RollDueToTopplingAngle - Specs.RollDueToCantAngle);
1072 			}
1073 			// apply pitching
1074 			if (CurrentCarSection >= 0 && CarSections[CurrentCarSection].Groups[0].Type == ObjectType.Overlay)
1075 			{
1076 				d.Rotate(s, Specs.PitchDueToAccelerationAngle);
1077 				Up.Rotate(s, Specs.PitchDueToAccelerationAngle);
1078 				Vector3 cc = new Vector3(0.5 * (FrontAxle.Follower.WorldPosition + RearAxle.Follower.WorldPosition));
1079 				FrontAxle.Follower.WorldPosition -= cc;
1080 				RearAxle.Follower.WorldPosition -= cc;
1081 				FrontAxle.Follower.WorldPosition.Rotate(s, Specs.PitchDueToAccelerationAngle);
1082 				RearAxle.Follower.WorldPosition.Rotate(s, Specs.PitchDueToAccelerationAngle);
1083 				FrontAxle.Follower.WorldPosition += cc;
1084 				RearAxle.Follower.WorldPosition += cc;
1085 			}
1086 
1087 			// spring sound
1088 			{
1089 				double a = Specs.RollDueToShakingAngle;
1090 				double diff = a - Sounds.SpringPlayedAngle;
1091 				const double angleTolerance = 0.001;
1092 				if (diff < -angleTolerance)
1093 				{
1094 					if (Sounds.SpringL != null && !Sounds.SpringL.IsPlaying)
1095 					{
1096 						Sounds.SpringL.Play(this, false);
1097 					}
1098 
1099 					Sounds.SpringPlayedAngle = a;
1100 				}
1101 				else if (diff > angleTolerance)
1102 				{
1103 					if (Sounds.SpringR != null && !Sounds.SpringR.IsPlaying)
1104 					{
1105 						Sounds.SpringR.Play(this, false);
1106 					}
1107 
1108 					Sounds.SpringPlayedAngle = a;
1109 				}
1110 			}
1111 			// flange sound
1112 			if (Sounds.Flange != null && Sounds.Flange.Count != 0)
1113 			{
1114 				/*
1115 				 * This determines the amount of flange noise as a result of the angle at which the
1116 				 * line that forms between the axles hits the rail, i.e. the less perpendicular that
1117 				 * line is to the rails, the more flange noise there will be.
1118 				 * */
1119 				Vector3 df = FrontAxle.Follower.WorldPosition - RearAxle.Follower.WorldPosition;
1120 				df.Normalize();
1121 				double b0 = df.X * RearAxle.Follower.WorldSide.X + df.Y * RearAxle.Follower.WorldSide.Y + df.Z * RearAxle.Follower.WorldSide.Z;
1122 				double b1 = df.X * FrontAxle.Follower.WorldSide.X + df.Y * FrontAxle.Follower.WorldSide.Y + df.Z * FrontAxle.Follower.WorldSide.Z;
1123 				double spd = Math.Abs(CurrentSpeed);
1124 				double pitch = 0.5 + 0.04 * spd;
1125 				double b2 = Math.Abs(b0) + Math.Abs(b1);
1126 				double basegain = 0.5 * b2 * b2 * spd * spd;
1127 				/*
1128 				 * This determines additional flange noise as a result of the roll angle of the car
1129 				 * compared to the roll angle of the rails, i.e. if the car bounces due to inaccuracies,
1130 				 * there will be additional flange noise.
1131 				 * */
1132 				double cdti = Math.Abs(FrontAxle.Follower.CantDueToInaccuracy) + Math.Abs(RearAxle.Follower.CantDueToInaccuracy);
1133 				basegain += 0.2 * spd * spd * cdti * cdti;
1134 				/*
1135 				 * This applies the settings.
1136 				 * */
1137 				if (basegain < 0.0) basegain = 0.0;
1138 				if (basegain > 0.75) basegain = 0.75;
1139 				if (pitch > Sounds.FlangePitch)
1140 				{
1141 					Sounds.FlangePitch += TimeElapsed;
1142 					if (Sounds.FlangePitch > pitch) Sounds.FlangePitch = pitch;
1143 				}
1144 				else
1145 				{
1146 					Sounds.FlangePitch -= TimeElapsed;
1147 					if (Sounds.FlangePitch < pitch) Sounds.FlangePitch = pitch;
1148 				}
1149 
1150 				pitch = Sounds.FlangePitch;
1151 				for (int i = 0; i < Sounds.Flange.Count; i++)
1152 				{
1153 					int key = Sounds.Flange.ElementAt(i).Key;
1154 					if(Sounds.Flange[key] == null)
1155 					{
1156 						continue;
1157 					}
1158 					if (key == this.FrontAxle.FlangeIndex | key == this.RearAxle.FlangeIndex)
1159 					{
1160 						Sounds.Flange[key].TargetVolume += TimeElapsed;
1161 						if (Sounds.Flange[key].TargetVolume > 1.0) Sounds.Flange[key].TargetVolume = 1.0;
1162 					}
1163 					else
1164 					{
1165 						Sounds.Flange[key].TargetVolume -= TimeElapsed;
1166 						if (Sounds.Flange[key].TargetVolume < 0.0) Sounds.Flange[key].TargetVolume = 0.0;
1167 					}
1168 
1169 					double gain = basegain * Sounds.Flange[key].TargetVolume;
1170 					if (Sounds.Flange[key].IsPlaying)
1171 					{
1172 						if (pitch > 0.01 & gain > 0.0001)
1173 						{
1174 							Sounds.Flange[key].Source.Pitch = pitch;
1175 							Sounds.Flange[key].Source.Volume = gain;
1176 						}
1177 						else
1178 						{
1179 							Sounds.Flange[key].Stop();
1180 						}
1181 					}
1182 					else if (pitch > 0.02 & gain > 0.01)
1183 					{
1184 						Sounds.Flange[key].Play(pitch, gain, this, true);
1185 					}
1186 				}
1187 			}
1188 		}
1189 
1190 		/// <summary>Updates the position of the camera relative to this car</summary>
UpdateCamera()1191 		public void UpdateCamera()
1192 		{
1193 			Vector3 direction = new Vector3(FrontAxle.Follower.WorldPosition - RearAxle.Follower.WorldPosition);
1194 			direction *= 1.0 / direction.Norm();
1195 			double sx = direction.Z * Up.Y - direction.Y * Up.Z;
1196 			double sy = direction.X * Up.Z - direction.Z * Up.X;
1197 			double sz = direction.Y * Up.X - direction.X * Up.Y;
1198 			double rx = 0.5 * (FrontAxle.Follower.WorldPosition.X + RearAxle.Follower.WorldPosition.X);
1199 			double ry = 0.5 * (FrontAxle.Follower.WorldPosition.Y + RearAxle.Follower.WorldPosition.Y);
1200 			double rz = 0.5 * (FrontAxle.Follower.WorldPosition.Z + RearAxle.Follower.WorldPosition.Z);
1201 			Vector3 cameraPosition;
1202 			Vector3 driverPosition = this.HasInteriorView ? Driver : this.baseTrain.Cars[this.baseTrain.DriverCar].Driver;
1203 			cameraPosition.X = rx + sx * driverPosition.X + Up.X * driverPosition.Y + direction.X * driverPosition.Z;
1204 			cameraPosition.Y = ry + sy * driverPosition.X + Up.Y * driverPosition.Y + direction.Y * driverPosition.Z;
1205 			cameraPosition.Z = rz + sz * driverPosition.X + Up.Z * driverPosition.Y + direction.Z * driverPosition.Z;
1206 
1207 			TrainManagerBase.Renderer.CameraTrackFollower.WorldPosition = cameraPosition;
1208 			TrainManagerBase.Renderer.CameraTrackFollower.WorldDirection = direction;
1209 			TrainManagerBase.Renderer.CameraTrackFollower.WorldUp = new Vector3(Up);
1210 			TrainManagerBase.Renderer.CameraTrackFollower.WorldSide = new Vector3(sx, sy, sz);
1211 			double f = (Driver.Z - RearAxle.Position) / (FrontAxle.Position - RearAxle.Position);
1212 			double tp = (1.0 - f) * RearAxle.Follower.TrackPosition + f * FrontAxle.Follower.TrackPosition;
1213 			TrainManagerBase.Renderer.CameraTrackFollower.UpdateAbsolute(tp, false, false);
1214 		}
1215 
UpdateSpeed(double TimeElapsed, double DecelerationDueToMotor, double DecelerationDueToBrake, out double Speed)1216 		public void UpdateSpeed(double TimeElapsed, double DecelerationDueToMotor, double DecelerationDueToBrake, out double Speed)
1217 		{
1218 
1219 			double PowerRollingCouplerAcceleration;
1220 			// rolling on an incline
1221 			{
1222 				double a = FrontAxle.Follower.WorldDirection.Y;
1223 				double b = RearAxle.Follower.WorldDirection.Y;
1224 				PowerRollingCouplerAcceleration =
1225 					-0.5 * (a + b) * TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity;
1226 			}
1227 			// friction
1228 			double FrictionBrakeAcceleration;
1229 			{
1230 				double v = Math.Abs(CurrentSpeed);
1231 				double t = Index == 0 & CurrentSpeed >= 0.0 || Index == baseTrain.NumberOfCars - 1 & CurrentSpeed <= 0.0 ? Specs.ExposedFrontalArea : Specs.UnexposedFrontalArea;
1232 				double a = FrontAxle.GetResistance(v, t, TrainManagerBase.CurrentRoute.Atmosphere.GetAirDensity(FrontAxle.Follower.WorldPosition.Y), TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity);
1233 				double b = RearAxle.GetResistance(v, t, TrainManagerBase.CurrentRoute.Atmosphere.GetAirDensity(FrontAxle.Follower.WorldPosition.Y), TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity);
1234 				FrictionBrakeAcceleration = 0.5 * (a + b);
1235 			}
1236 			// power
1237 			double wheelspin = 0.0;
1238 			double wheelSlipAccelerationMotorFront = 0.0;
1239 			double wheelSlipAccelerationMotorRear = 0.0;
1240 			double wheelSlipAccelerationBrakeFront = 0.0;
1241 			double wheelSlipAccelerationBrakeRear = 0.0;
1242 			if (!Derailed)
1243 			{
1244 				wheelSlipAccelerationMotorFront = FrontAxle.CriticalWheelSlipAccelerationForElectricMotor(TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity);
1245 				wheelSlipAccelerationMotorRear = RearAxle.CriticalWheelSlipAccelerationForElectricMotor(TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity);
1246 				wheelSlipAccelerationBrakeFront = FrontAxle.CriticalWheelSlipAccelerationForFrictionBrake(TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity);
1247 				wheelSlipAccelerationBrakeRear = RearAxle.CriticalWheelSlipAccelerationForFrictionBrake(TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity);
1248 			}
1249 
1250 			if (DecelerationDueToMotor == 0.0)
1251 			{
1252 				double a;
1253 				if (Specs.IsMotorCar)
1254 				{
1255 					if (DecelerationDueToMotor == 0.0)
1256 					{
1257 						if (baseTrain.Handles.Reverser.Actual != 0 & baseTrain.Handles.Power.Actual > 0 &
1258 						    !baseTrain.Handles.HoldBrake.Actual &
1259 						    !baseTrain.Handles.EmergencyBrake.Actual)
1260 						{
1261 							// target acceleration
1262 							if (baseTrain.Handles.Power.Actual - 1 < Specs.AccelerationCurves.Length)
1263 							{
1264 								// Load factor is a constant 1.0 for anything prior to BVE5
1265 								// This will need to be changed when the relevant branch is merged in
1266 								a = Specs.AccelerationCurves[baseTrain.Handles.Power.Actual - 1]
1267 									.GetAccelerationOutput(
1268 										(double) baseTrain.Handles.Reverser.Actual * CurrentSpeed,
1269 										1.0);
1270 							}
1271 							else
1272 							{
1273 								a = 0.0;
1274 							}
1275 
1276 							// readhesion device
1277 							if (a > ReAdhesionDevice.MaximumAccelerationOutput)
1278 							{
1279 								a = ReAdhesionDevice.MaximumAccelerationOutput;
1280 							}
1281 
1282 							// wheel slip
1283 							if (a < wheelSlipAccelerationMotorFront)
1284 							{
1285 								FrontAxle.CurrentWheelSlip = false;
1286 							}
1287 							else
1288 							{
1289 								FrontAxle.CurrentWheelSlip = true;
1290 								wheelspin += (double) baseTrain.Handles.Reverser.Actual * a * CurrentMass;
1291 							}
1292 
1293 							if (a < wheelSlipAccelerationMotorRear)
1294 							{
1295 								RearAxle.CurrentWheelSlip = false;
1296 							}
1297 							else
1298 							{
1299 								RearAxle.CurrentWheelSlip = true;
1300 								wheelspin += (double) baseTrain.Handles.Reverser.Actual * a * CurrentMass;
1301 							}
1302 
1303 							// Update readhesion device
1304 							this.ReAdhesionDevice.Update(a);
1305 							// Update constant speed device
1306 
1307 							this.ConstSpeed.Update(ref a, baseTrain.Specs.CurrentConstSpeed,
1308 								baseTrain.Handles.Reverser.Actual);
1309 
1310 							// finalize
1311 							if (wheelspin != 0.0) a = 0.0;
1312 						}
1313 						else
1314 						{
1315 							a = 0.0;
1316 							FrontAxle.CurrentWheelSlip = false;
1317 							RearAxle.CurrentWheelSlip = false;
1318 						}
1319 					}
1320 					else
1321 					{
1322 						a = 0.0;
1323 						FrontAxle.CurrentWheelSlip = false;
1324 						RearAxle.CurrentWheelSlip = false;
1325 					}
1326 				}
1327 				else
1328 				{
1329 					a = 0.0;
1330 					FrontAxle.CurrentWheelSlip = false;
1331 					RearAxle.CurrentWheelSlip = false;
1332 				}
1333 
1334 				if (!Derailed)
1335 				{
1336 					if (Specs.MotorAcceleration < a)
1337 					{
1338 						if (Specs.MotorAcceleration < 0.0)
1339 						{
1340 							Specs.MotorAcceleration += CarBrake.JerkDown * TimeElapsed;
1341 						}
1342 						else
1343 						{
1344 							Specs.MotorAcceleration += Specs.JerkPowerUp * TimeElapsed;
1345 						}
1346 
1347 						if (Specs.MotorAcceleration > a)
1348 						{
1349 							Specs.MotorAcceleration = a;
1350 						}
1351 					}
1352 					else
1353 					{
1354 						Specs.MotorAcceleration -= Specs.JerkPowerDown * TimeElapsed;
1355 						if (Specs.MotorAcceleration < a)
1356 						{
1357 							Specs.MotorAcceleration = a;
1358 						}
1359 					}
1360 				}
1361 				else
1362 				{
1363 					Specs.MotorAcceleration = 0.0;
1364 				}
1365 			}
1366 
1367 			// brake
1368 			bool wheellock = wheelspin == 0.0 & Derailed;
1369 			if (!Derailed & wheelspin == 0.0)
1370 			{
1371 				double a;
1372 				// motor
1373 				if (Specs.IsMotorCar & DecelerationDueToMotor != 0.0)
1374 				{
1375 					a = -DecelerationDueToMotor;
1376 					if (Specs.MotorAcceleration > a)
1377 					{
1378 						if (Specs.MotorAcceleration > 0.0)
1379 						{
1380 							Specs.MotorAcceleration -= Specs.JerkPowerDown * TimeElapsed;
1381 						}
1382 						else
1383 						{
1384 							Specs.MotorAcceleration -= CarBrake.JerkUp * TimeElapsed;
1385 						}
1386 
1387 						if (Specs.MotorAcceleration < a)
1388 						{
1389 							Specs.MotorAcceleration = a;
1390 						}
1391 					}
1392 					else
1393 					{
1394 						Specs.MotorAcceleration += CarBrake.JerkDown * TimeElapsed;
1395 						if (Specs.MotorAcceleration > a)
1396 						{
1397 							Specs.MotorAcceleration = a;
1398 						}
1399 					}
1400 				}
1401 
1402 				// brake
1403 				a = DecelerationDueToBrake;
1404 				if (CurrentSpeed >= -0.01 & CurrentSpeed <= 0.01)
1405 				{
1406 					double rf = FrontAxle.Follower.WorldDirection.Y;
1407 					double rr = RearAxle.Follower.WorldDirection.Y;
1408 					double ra = Math.Abs(0.5 * (rf + rr) *
1409 					                     TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity);
1410 					if (a > ra) a = ra;
1411 				}
1412 
1413 				double factor = EmptyMass / CurrentMass;
1414 				if (a >= wheelSlipAccelerationBrakeFront)
1415 				{
1416 					wheellock = true;
1417 				}
1418 				else
1419 				{
1420 					FrictionBrakeAcceleration += 0.5 * a * factor;
1421 				}
1422 
1423 				if (a >= wheelSlipAccelerationBrakeRear)
1424 				{
1425 					wheellock = true;
1426 				}
1427 				else
1428 				{
1429 					FrictionBrakeAcceleration += 0.5 * a * factor;
1430 				}
1431 			}
1432 			else if (Derailed)
1433 			{
1434 				FrictionBrakeAcceleration += TrainBase.CoefficientOfGroundFriction *
1435 				                             TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity;
1436 			}
1437 
1438 			// motor
1439 			if (baseTrain.Handles.Reverser.Actual != 0)
1440 			{
1441 				double factor = EmptyMass / CurrentMass;
1442 				if (Specs.MotorAcceleration > 0.0)
1443 				{
1444 					PowerRollingCouplerAcceleration +=
1445 						(double) baseTrain.Handles.Reverser.Actual * Specs.MotorAcceleration * factor;
1446 				}
1447 				else
1448 				{
1449 					double a = -Specs.MotorAcceleration;
1450 					if (a >= wheelSlipAccelerationMotorFront)
1451 					{
1452 						FrontAxle.CurrentWheelSlip = true;
1453 					}
1454 					else if (!Derailed)
1455 					{
1456 						FrictionBrakeAcceleration += 0.5 * a * factor;
1457 					}
1458 
1459 					if (a >= wheelSlipAccelerationMotorRear)
1460 					{
1461 						RearAxle.CurrentWheelSlip = true;
1462 					}
1463 					else
1464 					{
1465 						FrictionBrakeAcceleration += 0.5 * a * factor;
1466 					}
1467 				}
1468 			}
1469 			else
1470 			{
1471 				Specs.MotorAcceleration = 0.0;
1472 			}
1473 
1474 			// perceived speed
1475 			{
1476 				double target;
1477 				if (wheellock)
1478 				{
1479 					target = 0.0;
1480 				}
1481 				else if (wheelspin == 0.0)
1482 				{
1483 					target = CurrentSpeed;
1484 				}
1485 				else
1486 				{
1487 					target = CurrentSpeed + wheelspin / 2500.0;
1488 				}
1489 
1490 				double diff = target - Specs.PerceivedSpeed;
1491 				double rate = (diff < 0.0 ? 5.0 : 1.0) * TrainManagerBase.CurrentRoute.Atmosphere.AccelerationDueToGravity *
1492 				              TimeElapsed;
1493 				rate *= 1.0 - 0.7 / (diff * diff + 1.0);
1494 				double factor = rate * rate;
1495 				factor = 1.0 - factor / (factor + 1000.0);
1496 				rate *= factor;
1497 				if (diff >= -rate & diff <= rate)
1498 				{
1499 					Specs.PerceivedSpeed = target;
1500 				}
1501 				else
1502 				{
1503 					Specs.PerceivedSpeed += rate * Math.Sign(diff);
1504 				}
1505 			}
1506 			// calculate new speed
1507 			{
1508 				int d = Math.Sign(CurrentSpeed);
1509 				double a = PowerRollingCouplerAcceleration;
1510 				double b = FrictionBrakeAcceleration;
1511 				if (Math.Abs(a) < b)
1512 				{
1513 					if (Math.Sign(a) == d)
1514 					{
1515 						if (d == 0)
1516 						{
1517 							Speed = 0.0;
1518 						}
1519 						else
1520 						{
1521 							double c = (b - Math.Abs(a)) * TimeElapsed;
1522 							if (Math.Abs(CurrentSpeed) > c)
1523 							{
1524 								Speed = CurrentSpeed - d * c;
1525 							}
1526 							else
1527 							{
1528 								Speed = 0.0;
1529 							}
1530 						}
1531 					}
1532 					else
1533 					{
1534 						double c = (Math.Abs(a) + b) * TimeElapsed;
1535 						if (Math.Abs(CurrentSpeed) > c)
1536 						{
1537 							Speed = CurrentSpeed - d * c;
1538 						}
1539 						else
1540 						{
1541 							Speed = 0.0;
1542 						}
1543 					}
1544 				}
1545 				else
1546 				{
1547 					Speed = CurrentSpeed + (a - b * d) * TimeElapsed;
1548 				}
1549 			}
1550 		}
1551 
DetermineDoorClosingSpeed()1552 		public void DetermineDoorClosingSpeed()
1553 		{
1554 			if (Specs.DoorOpenFrequency <= 0.0)
1555 			{
1556 				if (Doors[0].OpenSound.Buffer != null & Doors[1].OpenSound.Buffer != null)
1557 				{
1558 					double a = Doors[0].OpenSound.Buffer.Duration;
1559 					double b = Doors[1].OpenSound.Buffer.Duration;
1560 					Specs.DoorOpenFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8;
1561 				}
1562 				else if (Doors[0].OpenSound.Buffer != null)
1563 				{
1564 					double a = Doors[0].OpenSound.Buffer.Duration;
1565 					Specs.DoorOpenFrequency = a > 0.0 ? 1.0 / a : 0.8;
1566 				}
1567 				else if (Doors[1].OpenSound.Buffer != null)
1568 				{
1569 					double b = Doors[1].OpenSound.Buffer.Duration;
1570 					Specs.DoorOpenFrequency = b > 0.0 ? 1.0 / b : 0.8;
1571 				}
1572 				else
1573 				{
1574 					Specs.DoorOpenFrequency = 0.8;
1575 				}
1576 			}
1577 
1578 			if (Specs.DoorCloseFrequency <= 0.0)
1579 			{
1580 				if (Doors[0].CloseSound.Buffer != null & Doors[1].CloseSound.Buffer != null)
1581 				{
1582 					double a = Doors[0].CloseSound.Buffer.Duration;
1583 					double b = Doors[1].CloseSound.Buffer.Duration;
1584 					Specs.DoorCloseFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8;
1585 				}
1586 				else if (Doors[0].CloseSound.Buffer != null)
1587 				{
1588 					double a = Doors[0].CloseSound.Buffer.Duration;
1589 					Specs.DoorCloseFrequency = a > 0.0 ? 1.0 / a : 0.8;
1590 				}
1591 				else if (Doors[1].CloseSound.Buffer != null)
1592 				{
1593 					double b = Doors[1].CloseSound.Buffer.Duration;
1594 					Specs.DoorCloseFrequency = b > 0.0 ? 1.0 / b : 0.8;
1595 				}
1596 				else
1597 				{
1598 					Specs.DoorCloseFrequency = 0.8;
1599 				}
1600 			}
1601 
1602 			const double f = 0.015;
1603 			const double g = 2.75;
1604 			Specs.DoorOpenPitch = Math.Exp(f * Math.Tan(g * (TrainManagerBase.RandomNumberGenerator.NextDouble() - 0.5)));
1605 			Specs.DoorClosePitch = Math.Exp(f * Math.Tan(g * (TrainManagerBase.RandomNumberGenerator.NextDouble() - 0.5)));
1606 			Specs.DoorOpenFrequency /= Specs.DoorOpenPitch;
1607 			Specs.DoorCloseFrequency /= Specs.DoorClosePitch;
1608 			/*
1609 			 * Remove the following two lines, then the pitch at which doors play
1610 			 * takes their randomized opening and closing times into account.
1611 			 * */
1612 			Specs.DoorOpenPitch = 1.0;
1613 			Specs.DoorClosePitch = 1.0;
1614 		}
1615 	}
1616 }
1617