diff --git a/Source/Documentation/Manual/cabs.rst b/Source/Documentation/Manual/cabs.rst index 1ef5eb8b24..ca16288b81 100644 --- a/Source/Documentation/Manual/cabs.rst +++ b/Source/Documentation/Manual/cabs.rst @@ -819,6 +819,8 @@ and the mirrors. The control blocks are like the one shown for the cab light. The Type strings are ORTS_LEFTDOOR, ORTS_RIGHTDOOR and ORTS_MIRRORS. +Animation for 2D cab windows is described :ref:`here ` . + .. _cabs-generic-items: Cab controls for generic items @@ -1025,6 +1027,7 @@ Rotation may be applied, with the same syntax, also to DigitalClock cab controls Display and animation of cabview controls in side views of 2D cabs ------------------------------------------------------------------ +.. _cabs-side-views: This is possible adding after the CabViewControls ( ) compound block an ORTSCabviewControls ( ) compound block, that has the same format as the @@ -1177,6 +1180,7 @@ Development Rules font. If no ace is specified, the default will be used. - Mirrors and doors can be operated from 3D cabs. The names used are ``LEFTDOOR``, ``RIGHTDOOR`` and ``MIRRORS``. +- Animation for 3D cab windows is described :ref:`here ` . - like the 2D cabs, also 3D cabs can have a night version. Night textures, named like the corresponding day textures, must be located within a ``NIGHT`` subfolder of the ``CABVIEW3D`` folder. To enable night cabs an ``.sd`` file with the same name as the diff --git a/Source/Documentation/Manual/driving.rst b/Source/Documentation/Manual/driving.rst index 73249ab0ae..5de7409db8 100644 --- a/Source/Documentation/Manual/driving.rst +++ b/Source/Documentation/Manual/driving.rst @@ -1088,10 +1088,24 @@ More information on connecting brakes and manipulating the brake hose connections can be found :ref:`here ` and :ref:`here `. -Doors and Mirror Commands -------------------------- - -Note that the standard keys in OR for these commands are different from +Doors, Mirror and Windows Commands +---------------------------------- +.. _driving-anim-commands: + +Note that these commands are active only if the trainset is equipped with +the related animations. + +=================== ===================================== +Command Function +=================== ===================================== +```` Door left open/close +```` Door right open/close +```` Mirror rotate clock/counterclockwise +```` Window left open/close +```` Window right open/close +=================== ===================================== + +Note that the standard keys for doors and mirror in OR are different from those of MSTS. Wheelslip Reset diff --git a/Source/Documentation/Manual/features-rollingstock.rst b/Source/Documentation/Manual/features-rollingstock.rst index 90e416a060..3479d97bd2 100644 --- a/Source/Documentation/Manual/features-rollingstock.rst +++ b/Source/Documentation/Manual/features-rollingstock.rst @@ -1234,6 +1234,181 @@ ENG or WAG file:: ) +.. _features-windows: + +Trainset windows +================ + + +Left and right 2D- or 3D-cab windows can be animated, showing the animation both in cabview and in +external views. For locomotives also two windows for the rear cab can be defined. + +The external sounds and the track sound are reproduced unattenuated with open window. +To have a volume difference, two lines as follows must be added to the wagon section of the .eng or .wag file:: + + ORTSExternalSoundPassedThroughPercent ( 30 ) + ORTSTrackSoundPassedThroughPercent ( 25 ) + +Numbers in parenthesis may vary from 0 (no sound heard internally) to 100 (sound heard unattenuated). +Note that, if these two lines are'nt added, but audio option "% of external sound heard internally" is set +to a value lower than 100, the above effect will be still available with external sounds, but not with +the track sound. + +Keyboard commands to toggle window state are listed :ref:`here ` . + +Names of the animations are as follows. + +Names for the windows animations as seen from the external camera views (the ones to be inserted in the .s +file of the trainset) must start with following strings:: + + LEFTWINDOWFRONT + RIGHTWINDOWFRONT + LEFTWINDOWREAR + RIGHTWINDOWREAR + +In case of carriages, only the first two apply. + +Names for the windows animations as seen from within a 2D cab (same names are valid for +front and rear cab); left and right are considered as seen from the related cab:: + + ORTS_2DEXTERNALLEFTWINDOW + ORTS_2DEXTERNALRIGHTTWINDOW + +Note that in general the lateral windows will be located in the side views of the 2D cab. +Therefore the related control blocks in the .cvf file will have to be located as described +:ref:`here ` . + +Names for the window animations as seen from within a 3D cab (the ones to be inserted in the .s +file of the 3D cab); Left and right are considered +as seen in the forward direction of the first cab. The convention difference between 2D and +3D cabs is due to the difference in the handling of the cabs. NOTE: these 4 controls are not +needed in the .cvf file (same applies also for wipers, doors and so on as seen from within a +3D cab):: + + ORTS_EXTERNALLEFTWINDOWFRONT + ORTS_EXTERNALRIGHTWINDOWFRONT + ORTS_EXTERNALLEFTWINDOWREAR + ORTS_EXTERNALRIGHTWINDOWREAR + +LEFTWINDOW and RIGHTWINDOW are the names of the controls that can be inserted in the +.cvf file and in the 3Dcab .s file to command the state change with the mouse. + +Here is an example of the animation of the left window in a 2D cab:: + + ORTSCabViewControls + ( 1 + ORTSAnimatedDisplay ( + Type ( ORTS_2DEXTERNALLEFTWINDOW MULTI_STATE_DISPLAY ) + Position ( 101 69 235 365 ) + Graphic ( ../../Common.Cab/CabE464/FinestraSX.ace ) + ORTSCycleTime ( 0.6 ) + States ( 16 4 4 + State ( + Style ( 0 ) + SwitchVal ( 0 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.0625 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.125 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.1875 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.25 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.3125 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.375 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.4375 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.5 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.5625 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.625 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.6875 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.75 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.825 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.88 ) + ) + State ( + Style ( 0 ) + SwitchVal ( 0.94 ) + ) + ) + ORTSCabviewpoint ( 1 ) + ) + ) + +FinestraSX.ace contains the various frames, from window fully close to window fully open. +ORTSCycleTime means that the opening/closing time of the window is 0.6 seconds. If one wants to use higher times, +more frames are needed to get a smooth animation. Note that, as explained above, +the control is within the ORTSCabviewControls block, which is skipped by MSTS and older OR versions to avoid +error messages, and note that the ORTSCabviewpoint ( 1 ) line specifies that that animation is in the left cabview. + +A simple control block to move a window by clicking the mouse can be as follows:: + + TwoState ( + Type ( ORTS_LEFTWINDOW TWO_STATE ) + Position ( 120 425 30 21 ) + Graphic ( cab.ace ) + NumFrames ( 2 2 1 ) + Style ( ONOFF ) + MouseControl ( 1 ) + ) + +If there is no specific window control in the real cab, you can locate this control on the window itself, +using a transparent graphic. So, clicking on the window, you change its state. This can be applied +both to 2D and 3D cabs. + +Sound triggers for windows animation are listed :ref:`here ` . + +Specifics for carriage window animations +---------------------------------------- + +One window to the left and one window to the right of the carriage may be animated. +They can be opened and closed only via keyboard (Ctrl-Q for left window and +Ctrl-Shift-Q for right window, as for locomotives). + +Note that the carriage must have only the main shape file (no passenger view shape file). +This main shape file can include also the inside structure of the carriage; note that when +a .wag or .eng file has an Inside block defined, and such block doesn't include a line +specifying the .s file, OR will use the main shape file to display the +inside. So such shape file will display the window animation both with +the passenger camera (inside view) and with the external cameras (outside views). + C# engine scripting =================== .. _features-scripting-csharp: diff --git a/Source/Documentation/Manual/sound.rst b/Source/Documentation/Manual/sound.rst index 212f99c915..c89c839ee7 100644 --- a/Source/Documentation/Manual/sound.rst +++ b/Source/Documentation/Manual/sound.rst @@ -406,6 +406,17 @@ Trigger Function Trigger 252 is activated when the braking system detects an emergency brake application and starts venting air from the Brake Pipe. +Following triggers are related to windows animation: + +.. _sound-windows: + +========= ===================================== +Trigger Function +========= ===================================== +260 WindowClosing +261 WindowOpening +========= ===================================== + Variable Triggers ----------------- diff --git a/Source/ORTS.Common/Input/UserCommand.cs b/Source/ORTS.Common/Input/UserCommand.cs index b86dc2bd61..8e1a8bb5af 100644 --- a/Source/ORTS.Common/Input/UserCommand.cs +++ b/Source/ORTS.Common/Input/UserCommand.cs @@ -163,6 +163,8 @@ public enum UserCommand [GetString("Control Pantograph 2")] ControlPantograph2, [GetString("Control Pantograph 3")] ControlPantograph3, [GetString("Control Pantograph 4")] ControlPantograph4, + [GetString("Control Window Left")] ControlWindowLeft, + [GetString("Control Window Right")] ControlWindowRight, [GetString("Control Battery Close")] ControlBatterySwitchClose, [GetString("Control Battery Open")] ControlBatterySwitchOpen, [GetString("Control Master Key")] ControlMasterKey, diff --git a/Source/ORTS.Settings/InputSettings.cs b/Source/ORTS.Settings/InputSettings.cs index af82b2874c..b9fa35eda4 100644 --- a/Source/ORTS.Settings/InputSettings.cs +++ b/Source/ORTS.Settings/InputSettings.cs @@ -374,6 +374,8 @@ static void InitializeCommands(UserCommandInput[] Commands) Commands[(int)UserCommand.ControlDieselPlayer] = new UserCommandKeyInput(0x15, KeyModifiers.Shift); Commands[(int)UserCommand.ControlDoorLeft] = new UserCommandKeyInput(0x10); Commands[(int)UserCommand.ControlDoorRight] = new UserCommandKeyInput(0x10, KeyModifiers.Shift); + Commands[(int)UserCommand.ControlWindowLeft] = new UserCommandKeyInput(0x10, KeyModifiers.Control); + Commands[(int)UserCommand.ControlWindowRight] = new UserCommandKeyInput(0x10, KeyModifiers.Control | KeyModifiers.Shift); Commands[(int)UserCommand.ControlDynamicBrakeDecrease] = new UserCommandKeyInput(0x33); Commands[(int)UserCommand.ControlDynamicBrakeIncrease] = new UserCommandKeyInput(0x34); Commands[(int)UserCommand.ControlElectricTrainSupply] = new UserCommandKeyInput(0x30, KeyModifiers.Alt); diff --git a/Source/Orts.Formats.Msts/CabViewFile.cs b/Source/Orts.Formats.Msts/CabViewFile.cs index 67d0a1df90..3f6100d758 100644 --- a/Source/Orts.Formats.Msts/CabViewFile.cs +++ b/Source/Orts.Formats.Msts/CabViewFile.cs @@ -184,6 +184,8 @@ public enum CABViewControlTypes ORTS_MIRRORS, ORTS_PANTOGRAPH3, ORTS_PANTOGRAPH4, + ORTS_LEFTWINDOW, + ORTS_RIGHTWINDOW, ORTS_LARGE_EJECTOR, ORTS_WATER_SCOOP, ORTS_HOURDIAL, @@ -206,6 +208,8 @@ public enum CABViewControlTypes ORTS_ELECTRIC_TRAIN_SUPPLY_COMMAND_SWITCH, ORTS_ELECTRIC_TRAIN_SUPPLY_ON, ORTS_2DEXTERNALWIPERS, + ORTS_2DEXTERNALLEFTWINDOW, + ORTS_2DEXTERNALRIGHTWINDOW, ORTS_GENERIC_ITEM1, ORTS_GENERIC_ITEM2, ORTS_SCREEN_SELECT, @@ -273,6 +277,10 @@ public enum CABViewControlTypes ORTS_ITEM2CONTINUOUS, ORTS_ITEM1TWOSTATE, ORTS_ITEM2TWOSTATE, + ORTS_EXTERNALLEFTWINDOWFRONT, + ORTS_EXTERNALRIGHTWINDOWFRONT, + ORTS_EXTERNALLEFTWINDOWREAR, + ORTS_EXTERNALRIGHTWINDOWREAR, } public enum CABViewControlStyles diff --git a/Source/Orts.Simulation/Common/Commands.cs b/Source/Orts.Simulation/Common/Commands.cs index 5e47dc944a..f9b61c0557 100644 --- a/Source/Orts.Simulation/Common/Commands.cs +++ b/Source/Orts.Simulation/Common/Commands.cs @@ -1243,6 +1243,47 @@ public override void Redo() } } + [Serializable()] + public sealed class ToggleWindowLeftCommand : Command + { + public static MSTSWagon Receiver { get; set; } + + public ToggleWindowLeftCommand(CommandLog log) + : base(log) + { + Redo(); + } + + public override void Redo() + { + if (Receiver is MSTSLocomotive locomotive && locomotive.UsingRearCab) + locomotive.ToggleWindow(rear: true, left: false); + else + Receiver.ToggleWindow(rear: false, left: true); + } + } + + [Serializable()] + public sealed class ToggleWindowRightCommand : Command + { + public static MSTSWagon Receiver { get; set; } + + public ToggleWindowRightCommand(CommandLog log) + : base(log) + { + Redo(); + } + + public override void Redo() + { + if (Receiver is MSTSLocomotive locomotive && locomotive.UsingRearCab) + locomotive.ToggleWindow(rear: true, left: true); + else + Receiver.ToggleWindow(rear: false, left: false); + } + } + + [Serializable()] public sealed class ToggleBatterySwitchCommand : BooleanCommand { diff --git a/Source/Orts.Simulation/Common/Events.cs b/Source/Orts.Simulation/Common/Events.cs index a1cce35972..f7d5c4d700 100644 --- a/Source/Orts.Simulation/Common/Events.cs +++ b/Source/Orts.Simulation/Common/Events.cs @@ -198,6 +198,10 @@ public enum Event VigilanceAlarmReset, WaterScoopDown, WaterScoopUp, + WindowClosing, + WindowOpening, + WindowsClosed, + WindowsOpen, WiperOff, WiperOn, _HeadlightDim, @@ -526,6 +530,11 @@ public static Event From(Source source, int eventID) case 251: return Event.OverchargeBrakingOff; case 252: return Event.EmergencyVentValveOn; + case 260: return Event.WindowClosing; + case 261: return Event.WindowOpening; + case 262: return Event.WindowsClosed; + case 263: return Event.WindowsOpen; + // Cruise Control case 298: return Event.LeverFromZero; case 299: return Event.LeverToZero; diff --git a/Source/Orts.Simulation/Simulation/Confirmer.cs b/Source/Orts.Simulation/Simulation/Confirmer.cs index 406eaafdd0..b5b07eb59b 100644 --- a/Source/Orts.Simulation/Simulation/Confirmer.cs +++ b/Source/Orts.Simulation/Simulation/Confirmer.cs @@ -107,6 +107,8 @@ public enum CabControl { , DoorsLeft , DoorsRight , Mirror + , WindowLeft + , WindowRight // Track Devices , SwitchAhead , SwitchBehind @@ -243,7 +245,7 @@ public Confirmer(Simulator simulator, double defaultDurationS) , new string [] { GetString("Bell"), GetString("off"), null, GetString("ring") } , new string [] { GetString("Headlight"), GetString("off"), GetString("dim"), GetString("bright") } , new string [] { GetString("Cab Light"), GetString("off"), null, GetString("on") } - , new string [] { GetString("Wipers"), GetString("off"), null, GetString("on") } + , new string [] { GetString("Wipers"), GetString("off"), null, GetString("on") } , new string [] { GetString("Cab"), null, null, GetParticularString("Cab", "change"), null, null, GetString("changing is not available"), GetString("changing disabled. Close throttle, set reverser to neutral, stop train then re-try.") } , new string [] { GetString("Odometer"), null, null, GetParticularString("Odometer", "reset"), GetParticularString("Odometer", "counting down"), GetParticularString("Odometer", "counting up") } , new string [] { GetString("Battery"), GetString("off"), null, GetString("on") } @@ -254,7 +256,9 @@ public Confirmer(Simulator simulator, double defaultDurationS) // Train Devices , new string [] { GetString("Doors Left"), GetString("close"), null, GetString("open") } , new string [] { GetString("Doors Right"), GetString("close"), null, GetString("open") } - , new string [] { GetString("Mirror"), GetString("retract"), null, GetString("extend") } + , new string [] { GetString("Mirror"), GetString("retract"), null, GetString("extend") } + , new string [] { GetString("Window Left"), GetString("closing"), null, GetString("opening") } + , new string [] { GetString("Window Right"), GetString("closing"), null, GetString("opening") } // Track Devices , new string [] { GetString("Switch Ahead"), null, null, GetParticularString("Switch", "change"), null, null, GetString("locked. Use Control+M to change signals to manual mode then re-try.") } , new string [] { GetString("Switch Behind"), null, null, GetParticularString("Switch", "change"), null, null, GetString("locked. Use Control+M to change signals to manual mode then re-try.") } diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs index 07db660f58..6bcf2a76d9 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs @@ -5722,6 +5722,16 @@ public virtual float GetDataOf(CabViewControl cvc) data = state >= DoorState.Opening ? 1 : 0; } break; + case CABViewControlTypes.ORTS_LEFTWINDOW: + case CABViewControlTypes.ORTS_2DEXTERNALLEFTWINDOW: + data = UsingRearCab ? (WindowStates[RightWindowRearIndex] == WindowState.Closing || WindowStates[RightWindowRearIndex] == WindowState.Opening ? 1 : 0) : + (WindowStates[LeftWindowFrontIndex] == WindowState.Closing || WindowStates[LeftWindowFrontIndex] == WindowState.Opening ? 1 : 0); + break; + case CABViewControlTypes.ORTS_RIGHTWINDOW: + case CABViewControlTypes.ORTS_2DEXTERNALRIGHTWINDOW: + data = UsingRearCab ? (WindowStates[LeftWindowRearIndex] == WindowState.Closing || WindowStates[LeftWindowRearIndex] == WindowState.Opening ? 1 : 0) : + (WindowStates[RightWindowFrontIndex] == WindowState.Closing || WindowStates[RightWindowFrontIndex] == WindowState.Opening ? 1 : 0); + break; case CABViewControlTypes.ORTS_MIRRORS: data = MirrorOpen ? 1 : 0; break; diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs index 05d02ad779..a1dbdcec26 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs @@ -72,6 +72,23 @@ public class MSTSWagon : TrainCar public Doors Doors; public Door RightDoor => Doors.RightDoor; public Door LeftDoor => Doors.LeftDoor; + + public enum WindowState + // Don't change the order of entries within this enum + { + Closed, + Closing, + Opening, + Open, + } + + public static int LeftWindowFrontIndex = 0; + public static int RightWindowFrontIndex = 1; + public static int LeftWindowRearIndex = 2; + public static int RightWindowRearIndex = 3; + public WindowState[] WindowStates = new WindowState[4]; + public float[] SoundHeardInternallyCorrection = new float[2]; + public bool MirrorOpen; public bool UnloadingPartsOpen; public bool WaitForAnimationReady; // delay counter to start loading/unliading is on; @@ -1814,6 +1831,10 @@ public override void Save(BinaryWriter outf) outf.Write(DerailPossible); outf.Write(DerailExpected); outf.Write(DerailElapsedTimeS); + for (int index = 0; index < 4; index++) + { + outf.Write((int)WindowStates[index]); + } LocomotiveAxles.Save(outf); @@ -1869,6 +1890,10 @@ public override void Restore(BinaryReader inf) DerailPossible = inf.ReadBoolean(); DerailExpected = inf.ReadBoolean(); DerailElapsedTimeS = inf.ReadSingle(); + for (int index = 0; index < 4; index++) + { + WindowStates[index] = (WindowState)inf.ReadInt32(); + } MoveParamsToAxle(); LocomotiveAxles.Restore(inf); @@ -3498,6 +3523,22 @@ public void ToggleMirrors() if (Simulator.PlayerLocomotive == this) Simulator.Confirmer.Confirm(CabControl.Mirror, MirrorOpen ? CabSetting.On : CabSetting.Off); } + public void ToggleWindow(bool rear, bool left) + { + var open = false; + var index = (left ? 0 : 1) + 2 * (rear ? 1 : 0); + if (WindowStates[index] == WindowState.Closed || WindowStates[index] == WindowState.Closing) + WindowStates[index] = WindowState.Opening; + else if (WindowStates[index] == WindowState.Open || WindowStates[index] == WindowState.Opening) + WindowStates[index] = WindowState.Closing; + if (WindowStates[index] == WindowState.Opening) open = true; + + + if (open) SignalEvent(Event.WindowOpening); // hook for sound trigger + else SignalEvent(Event.WindowClosing); + if (Simulator.PlayerLocomotive == this) Simulator.Confirmer.Confirm(left ^ rear ? CabControl.WindowLeft : CabControl.WindowRight, open ? CabSetting.On : CabSetting.Off); + } + public void FindControlActiveLocomotive() { // Find the active locomotive associated with a control car diff --git a/Source/Orts.Simulation/Simulation/Simulator.cs b/Source/Orts.Simulation/Simulation/Simulator.cs index 33fb03cc51..20db0cb922 100644 --- a/Source/Orts.Simulation/Simulation/Simulator.cs +++ b/Source/Orts.Simulation/Simulation/Simulator.cs @@ -699,6 +699,8 @@ public void SetCommandReceivers() ToggleDoorsLeftCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; ToggleDoorsRightCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; ToggleMirrorsCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; + ToggleWindowLeftCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; + ToggleWindowRightCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; CabRadioCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; ToggleHelpersEngineCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; BatterySwitchCommand.Receiver = (PlayerLocomotive as MSTSLocomotive).LocomotivePowerSupply; @@ -731,6 +733,12 @@ public void SetCommandReceivers() EOTMountCommand.Receiver = (MSTSLocomotive)PlayerLocomotive; } + public void SetWagonCommandReceivers(MSTSWagon wag) + { + ToggleWindowLeftCommand.Receiver = wag; + ToggleWindowRightCommand.Receiver = wag; + } + public TrainCar SetPlayerLocomotive(Train playerTrain) { TrainCar PlayerLocomotive = null; diff --git a/Source/RunActivity/Viewer3D/AnimatedPart.cs b/Source/RunActivity/Viewer3D/AnimatedPart.cs index e1507b31c9..15471d4f26 100644 --- a/Source/RunActivity/Viewer3D/AnimatedPart.cs +++ b/Source/RunActivity/Viewer3D/AnimatedPart.cs @@ -160,6 +160,24 @@ public void UpdateState(bool state, ElapsedTime elapsedTime) SetFrameClamp(AnimationKey + (state ? 1 : -1) * elapsedTime.ClockSeconds); } + /// + /// Updates an animated part that toggles between two states and returns relative value of + /// animation key (between 0 and 1). + /// + public float UpdateAndReturnState(bool state, ElapsedTime elapsedTime) + { + SetFrameClamp(AnimationKey + (state ? 1 : -1) * elapsedTime.ClockSeconds); + return AnimationKey / FrameCount; + } + + /// + /// Returns the animation key fraction (between 0 and 1) + /// + public float AnimationKeyFraction() + { + return AnimationKey / FrameCount; + } + /// /// Updates an animated part that loops (e.g. running gear), changing by the given amount. /// diff --git a/Source/RunActivity/Viewer3D/Cameras.cs b/Source/RunActivity/Viewer3D/Cameras.cs index 07adb2ff7c..f6883a5e15 100644 --- a/Source/RunActivity/Viewer3D/Cameras.cs +++ b/Source/RunActivity/Viewer3D/Cameras.cs @@ -857,6 +857,7 @@ protected virtual List GetCameraCars() protected virtual void SetCameraCar(TrainCar car) { attachedCar = car; + Viewer.Simulator.SetWagonCommandReceivers((MSTSWagon)car); } protected virtual bool IsCameraFlipped() diff --git a/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs b/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs index e22de56df3..a36b292c53 100644 --- a/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs +++ b/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs @@ -2192,6 +2192,8 @@ public virtual int GetDrawIndex() case CABViewControlTypes.LEFTDOOR: case CABViewControlTypes.RIGHTDOOR: case CABViewControlTypes.MIRRORS: + case CABViewControlTypes.ORTS_LEFTWINDOW: + case CABViewControlTypes.ORTS_RIGHTWINDOW: case CABViewControlTypes.HORN: case CABViewControlTypes.VACUUM_EXHAUSTER: case CABViewControlTypes.WHISTLE: @@ -2573,6 +2575,20 @@ public void HandleUserInput() } ButtonState = buttonState; break; + case CABViewControlTypes.ORTS_LEFTWINDOW: + case CABViewControlTypes.ORTS_RIGHTWINDOW: + { + bool left = (Control.ControlType.Type == CABViewControlTypes.ORTS_LEFTWINDOW); + var windowIndex = (left ? 0 : 1) + 2 * (Locomotive.UsingRearCab ? 1 : 0); + var state = Locomotive.WindowStates[windowIndex]; + int open = state >= MSTSWagon.WindowState.Opening ? 1 : 0; + if (open != ChangedValue(open)) + { + if (left) new ToggleWindowLeftCommand(Viewer.Log); + else new ToggleWindowRightCommand(Viewer.Log); + } + } + break; // Train Control System controls case CABViewControlTypes.ORTS_TCS: @@ -2861,7 +2877,10 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) if (animate) AnimationOn = true; - int index; + int index = 0; + switch (ControlDiscrete.ControlType.Type) + { + case CABViewControlTypes.ORTS_2DEXTERNALWIPERS: var halfCycleS = CycleTimeS / 2f; if (AnimationOn) { @@ -2875,9 +2894,61 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) else index = PercentToIndex((CycleTimeS - CumulativeTime) / halfCycleS); } + break; + + case CABViewControlTypes.ORTS_2DEXTERNALLEFTWINDOW: + case CABViewControlTypes.ORTS_2DEXTERNALRIGHTWINDOW: + var windowIndex = Locomotive.UsingRearCab ? MSTSWagon.RightWindowRearIndex : MSTSWagon.LeftWindowFrontIndex; + var soundCorrectionIndex = windowIndex; + if (ControlDiscrete.ControlType.Type == CABViewControlTypes.ORTS_2DEXTERNALRIGHTWINDOW) + { + windowIndex = Locomotive.UsingRearCab ? MSTSWagon.LeftWindowRearIndex : MSTSWagon.RightWindowFrontIndex; + } + Locomotive.SoundHeardInternallyCorrection[soundCorrectionIndex] = 0; + if (AnimationOn) + { + CumulativeTime += elapsedTime.ClockSeconds; + if (CumulativeTime >= CycleTimeS) + { + AnimationOn = false; + CumulativeTime = CycleTimeS; + } + if (Locomotive.WindowStates[windowIndex] == MSTSWagon.WindowState.Opening) + { + index = PercentToIndex(CumulativeTime / CycleTimeS); + Locomotive.SoundHeardInternallyCorrection[soundCorrectionIndex] = CumulativeTime / CycleTimeS; + if (!AnimationOn) + { + Locomotive.WindowStates[windowIndex] = MSTSWagon.WindowState.Open; + CumulativeTime = 0; + } + } else { - index = 0; + index = PercentToIndex((CycleTimeS - CumulativeTime) / CycleTimeS); + Locomotive.SoundHeardInternallyCorrection[soundCorrectionIndex] = (CycleTimeS - CumulativeTime) / CycleTimeS; + if (!AnimationOn) + { + Locomotive.WindowStates[windowIndex] = MSTSWagon.WindowState.Closed; + CumulativeTime = 0; + } + } + } + else + { + CumulativeTime = 0; + if (Locomotive.WindowStates[windowIndex] == MSTSWagon.WindowState.Open) + { + index = PercentToIndex(1); + Locomotive.SoundHeardInternallyCorrection[soundCorrectionIndex] = 1; + } + else + { + index = PercentToIndex(0); + Locomotive.SoundHeardInternallyCorrection[soundCorrectionIndex] = 0; + } + } + break; } PrepareFrameForIndex(frame, elapsedTime, index); @@ -3255,6 +3326,10 @@ public ThreeDimentionCabViewer(Viewer viewer, MSTSLocomotive car, MSTSLocomotive case CABViewControlTypes.ORTS_ITEM2CONTINUOUS: case CABViewControlTypes.ORTS_ITEM1TWOSTATE: case CABViewControlTypes.ORTS_ITEM2TWOSTATE: + case CABViewControlTypes.ORTS_EXTERNALLEFTWINDOWFRONT: + case CABViewControlTypes.ORTS_EXTERNALRIGHTWINDOWFRONT: + case CABViewControlTypes.ORTS_EXTERNALLEFTWINDOWREAR: + case CABViewControlTypes.ORTS_EXTERNALRIGHTWINDOWREAR: //cvf file has no external wipers, left door, right door and mirrors key word break; default: @@ -3335,6 +3410,8 @@ public override void HandleUserInput(ElapsedTime elapsedTime) /// public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) { + + Locomotive.SoundHeardInternallyCorrection[0] = Locomotive.SoundHeardInternallyCorrection[1] = 0; foreach (var p in AnimateParts) { if (p.Value.Type.Type >= CABViewControlTypes.EXTERNALWIPERS) //for wipers, doors and mirrors @@ -3355,6 +3432,18 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) case CABViewControlTypes.MIRRORS: p.Value.UpdateState(Locomotive.MirrorOpen, elapsedTime); break; + case CABViewControlTypes.ORTS_EXTERNALLEFTWINDOWFRONT: + PrepareFrameForWindow(MSTSWagon.LeftWindowFrontIndex, p.Value, elapsedTime); + break; + case CABViewControlTypes.ORTS_EXTERNALRIGHTWINDOWFRONT: + PrepareFrameForWindow(MSTSWagon.RightWindowFrontIndex, p.Value, elapsedTime); + break; + case CABViewControlTypes.ORTS_EXTERNALLEFTWINDOWREAR: + PrepareFrameForWindow(MSTSWagon.LeftWindowRearIndex, p.Value, elapsedTime); + break; + case CABViewControlTypes.ORTS_EXTERNALRIGHTWINDOWREAR: + PrepareFrameForWindow(MSTSWagon.RightWindowRearIndex, p.Value, elapsedTime); + break; case CABViewControlTypes.ORTS_ITEM1CONTINUOUS: p.Value.UpdateLoop(Locomotive.GenericItem1, elapsedTime); break; @@ -3463,6 +3552,19 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) TrainCarShape.ConditionallyPrepareFrame(frame, elapsedTime, MatrixVisible); } + internal void PrepareFrameForWindow(int windowIndex, AnimatedPartMultiState anim, ElapsedTime elapsedTime) + { + if (Locomotive.WindowStates[windowIndex] == MSTSWagon.WindowState.Closed) anim.SetState(false); + else if (Locomotive.WindowStates[windowIndex] == MSTSWagon.WindowState.Open) anim.SetState(true); + var animationFraction = anim.UpdateAndReturnState(Locomotive.WindowStates[windowIndex] >= MSTSWagon.WindowState.Opening, elapsedTime); + if (animationFraction == 0 && Locomotive.WindowStates[windowIndex] < MSTSWagon.WindowState.Opening) + Locomotive.WindowStates[windowIndex] = MSTSWagon.WindowState.Closed; + else if (animationFraction == 1 && Locomotive.WindowStates[windowIndex] >= MSTSWagon.WindowState.Opening) + Locomotive.WindowStates[windowIndex] = MSTSWagon.WindowState.Open; + if (Locomotive.UsingRearCab ^ windowIndex < 2) + Locomotive.SoundHeardInternallyCorrection[windowIndex > 1 ? windowIndex - 2 : windowIndex] = animationFraction; + } + internal override void Mark() { TrainCarShape?.Mark(); diff --git a/Source/RunActivity/Viewer3D/RollingStock/MSTSWagonViewer.cs b/Source/RunActivity/Viewer3D/RollingStock/MSTSWagonViewer.cs index 6341d4bef4..84bcafa779 100644 --- a/Source/RunActivity/Viewer3D/RollingStock/MSTSWagonViewer.cs +++ b/Source/RunActivity/Viewer3D/RollingStock/MSTSWagonViewer.cs @@ -70,6 +70,10 @@ public class MSTSWagonViewer : TrainCarViewer AnimatedPart LeftDoor; AnimatedPart RightDoor; AnimatedPart Mirrors; + protected AnimatedPart LeftWindowFront; + protected AnimatedPart RightWindowFront; + protected AnimatedPart LeftWindowRear; + protected AnimatedPart RightWindowRear; protected AnimatedPart Wipers; protected AnimatedPart Bell; protected AnimatedPart Item1Continuous; @@ -321,6 +325,10 @@ public MSTSWagonViewer(Viewer viewer, MSTSWagon car) LeftDoor = new AnimatedPart(TrainCarShape); RightDoor = new AnimatedPart(TrainCarShape); Mirrors = new AnimatedPart(TrainCarShape); + LeftWindowFront = new AnimatedPart(TrainCarShape); + RightWindowFront = new AnimatedPart(TrainCarShape); + LeftWindowRear = new AnimatedPart(TrainCarShape); + RightWindowRear = new AnimatedPart(TrainCarShape); Wipers = new AnimatedPart(TrainCarShape); UnloadingParts = new AnimatedPart(TrainCarShape); Bell = new AnimatedPart(TrainCarShape); @@ -413,6 +421,10 @@ public MSTSWagonViewer(Viewer viewer, MSTSWagon car) LeftDoor.SetState(MSTSWagon.LeftDoor.State >= DoorState.Opening); RightDoor.SetState(MSTSWagon.RightDoor.State >= DoorState.Opening); Mirrors.SetState(MSTSWagon.MirrorOpen); + LeftWindowFront.SetState(MSTSWagon.WindowStates[MSTSWagon.LeftWindowFrontIndex] >= MSTSWagon.WindowState.Opening); + RightWindowFront.SetState(MSTSWagon.WindowStates[MSTSWagon.RightWindowFrontIndex] >= MSTSWagon.WindowState.Opening); + LeftWindowRear.SetState(MSTSWagon.WindowStates[MSTSWagon.LeftWindowRearIndex] >= MSTSWagon.WindowState.Opening); + RightWindowRear.SetState(MSTSWagon.WindowStates[MSTSWagon.RightWindowRearIndex] >= MSTSWagon.WindowState.Opening); Item1TwoState.SetState(MSTSWagon.GenericItem1); Item2TwoState.SetState(MSTSWagon.GenericItem2); UnloadingParts.SetState(MSTSWagon.UnloadingPartsOpen); @@ -531,6 +543,22 @@ void MatchMatrixToPart(MSTSWagon car, int matrix, int bogieMatrix) { Mirrors.AddMatrix(matrix); } + else if (matrixName.StartsWith("LEFTWINDOWFRONT")) // Windows + { + LeftWindowFront.AddMatrix(matrix); + } + else if (matrixName.StartsWith("RIGHTWINDOWFRONT")) // Windows + { + RightWindowFront.AddMatrix(matrix); + } + else if (matrixName.StartsWith("LEFTWINDOWREAR")) // Windows + { + LeftWindowRear.AddMatrix(matrix); + } + else if (matrixName.StartsWith("RIGHTWINDOWREAR")) // Windows + { + RightWindowRear.AddMatrix(matrix); + } else if (matrixName.StartsWith("UNLOADINGPARTS")) // unloading parts { UnloadingParts.AddMatrix(matrix); @@ -592,6 +620,8 @@ public override void InitializeUserInputCommands() UserInputCommands.Add(UserCommand.ControlDoorLeft, new Action[] { Noop, () => new ToggleDoorsLeftCommand(Viewer.Log) }); UserInputCommands.Add(UserCommand.ControlDoorRight, new Action[] { Noop, () => new ToggleDoorsRightCommand(Viewer.Log) }); UserInputCommands.Add(UserCommand.ControlMirror, new Action[] { Noop, () => new ToggleMirrorsCommand(Viewer.Log) }); + UserInputCommands.Add(UserCommand.ControlWindowLeft, new Action[] { Noop, () => new ToggleWindowLeftCommand(Viewer.Log) }); + UserInputCommands.Add(UserCommand.ControlWindowRight, new Action[] { Noop, () => new ToggleWindowRightCommand(Viewer.Log) }); } public override void HandleUserInput(ElapsedTime elapsedTime) @@ -615,6 +645,10 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) LeftDoor.UpdateState(MSTSWagon.LeftDoor.State >= DoorState.Opening, elapsedTime); RightDoor.UpdateState(MSTSWagon.RightDoor.State >= DoorState.Opening, elapsedTime); Mirrors.UpdateState(MSTSWagon.MirrorOpen, elapsedTime); + LeftWindowFront.UpdateState(MSTSWagon.WindowStates[MSTSWagon.LeftWindowFrontIndex] >= MSTSWagon.WindowState.Opening, elapsedTime); + RightWindowFront.UpdateState(MSTSWagon.WindowStates[MSTSWagon.RightWindowFrontIndex] >= MSTSWagon.WindowState.Opening, elapsedTime); + LeftWindowRear.UpdateState(MSTSWagon.WindowStates[MSTSWagon.LeftWindowRearIndex] >= MSTSWagon.WindowState.Opening, elapsedTime); + RightWindowRear.UpdateState(MSTSWagon.WindowStates[MSTSWagon.RightWindowRearIndex] >= MSTSWagon.WindowState.Opening, elapsedTime); UnloadingParts.UpdateState(MSTSWagon.UnloadingPartsOpen, elapsedTime); Item1TwoState.UpdateState(MSTSWagon.GenericItem1, elapsedTime); Item2TwoState.UpdateState(MSTSWagon.GenericItem2, elapsedTime); @@ -699,6 +733,13 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) foreach (ParticleEmitterViewer drawer in drawers) drawer.PrepareFrame(frame, elapsedTime); + if (!(car is MSTSLocomotive) && (LeftWindowFront.FrameCount > 0 || RightWindowFront.FrameCount > 0)) + { + if (LeftWindowFront.FrameCount > 0) + car.SoundHeardInternallyCorrection[MSTSWagon.LeftWindowFrontIndex] = LeftWindowFront.AnimationKeyFraction(); + if (RightWindowFront.FrameCount > 0) + car.SoundHeardInternallyCorrection[MSTSWagon.RightWindowFrontIndex] = RightWindowFront.AnimationKeyFraction(); + } } diff --git a/Source/RunActivity/Viewer3D/Sound.cs b/Source/RunActivity/Viewer3D/Sound.cs index 169b46a96e..991c876ad0 100644 --- a/Source/RunActivity/Viewer3D/Sound.cs +++ b/Source/RunActivity/Viewer3D/Sound.cs @@ -1420,17 +1420,19 @@ private void SetFreqAndVolume() volume *= Interpolate(x, MSTSStream.VolumeCurves[i]); } + var wag = (MSTSWagon)SoundSource.Viewer.Camera.AttachedCar; + var soundHeardInternallyCorrection = Math.Min(wag.SoundHeardInternallyCorrection[0] + wag.SoundHeardInternallyCorrection[1], 1); if (SoundSource.IsExternal && SoundSource.Viewer.Camera.Style != Camera.Styles.External && !SoundSource.IsUnattenuated) { - if (SoundSource.Viewer.Camera.AttachedCar == null || ((MSTSWagon)SoundSource.Viewer.Camera.AttachedCar).ExternalSoundPassThruPercent == -1) - volume *= Program.Viewer.Settings.ExternalSoundPassThruPercent * 0.01f; - else volume *= ((MSTSWagon)SoundSource.Viewer.Camera.AttachedCar).ExternalSoundPassThruPercent * 0.01f; + if (wag == null || wag.ExternalSoundPassThruPercent == -1) + volume *= Program.Viewer.Settings.ExternalSoundPassThruPercent * 0.01f + (1 - Program.Viewer.Settings.ExternalSoundPassThruPercent * 0.01f) * soundHeardInternallyCorrection; + else volume *= wag.ExternalSoundPassThruPercent * 0.01f + (1 - wag.ExternalSoundPassThruPercent * 0.01f) * soundHeardInternallyCorrection; } if (SoundSource.IsInternalTrackSound && SoundSource.Viewer.Camera.Style != Camera.Styles.External) { - if (((MSTSWagon)SoundSource.Viewer.Camera.AttachedCar)?.TrackSoundPassThruPercent != -1) - volume *= ((MSTSWagon)SoundSource.Viewer.Camera.AttachedCar).TrackSoundPassThruPercent * 0.01f; + if (wag?.TrackSoundPassThruPercent != -1) + volume *= wag.TrackSoundPassThruPercent * 0.01f + (1 - wag.TrackSoundPassThruPercent * 0.01f) * soundHeardInternallyCorrection; } ALSoundSource.Volume = volume;