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