internal Windscreen(int numberOfDrops, double dropLife, TrainManager.Car car) { RainDrops = new Raindrop[numberOfDrops]; DropLife = dropLife; currentDrops = 0; Car = car; }
internal static void DebugTouchArea() { if (!Loading.SimulationSetup) { return; } if (World.CameraMode != CameraViewMode.Interior && World.CameraMode != CameraViewMode.InteriorLookAhead) { return; } TrainManager.Car Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; int add = Car.CarSections[0].CurrentAdditionalGroup + 1; if (add < Car.CarSections[0].Groups.Length) { TrainManager.TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; if (TouchElements != null) { foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; ShowObjectSelection(o); } ResetOpenGlState(); GL.PushMatrix(); UpdateViewport(ViewPortChangeMode.ChangeToCab); RenderSceneSelection(true); GL.PopMatrix(); foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; HideObjectSelection(o); } } } }
internal static void Parse(string fileName, ref TrainManager.Car car) { //3D center of the car Vector3 center = new Vector3(0.0, 0.0, 0.0); //Positioned to the left of the car, but centered Y & Z Vector3 left = new Vector3(-1.3, 0.0, 0.0); //Positioned to the right of the car, but centered Y & Z Vector3 right = new Vector3(1.3, 0.0, 0.0); //Positioned at the front of the car, centered X and Y Vector3 front = new Vector3(0.0, 0.0, 0.5 * car.Length); //Positioned at the position of the panel / 3D cab (Remember that the panel is just an object in the world...) Vector3 panel = new Vector3(car.Driver.X, car.Driver.Y, car.Driver.Z + 1.0); //The current XML file to load XmlDocument currentXML = new XmlDocument(); //Load the marker's XML file currentXML.Load(fileName); currentPath = System.IO.Path.GetDirectoryName(fileName); if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/openBVE/CarSounds"); if (DocumentNodes == null || DocumentNodes.Count == 0) { Interface.AddMessage(Interface.MessageType.Error, false, "No car sound nodes defined in XML file " + fileName); //If we have no appropriate nodes specified, return false and fallback to loading the legacy Sound.cfg file throw new Exception("Empty sound.xml file"); } foreach (XmlNode n in DocumentNodes) { if (n.HasChildNodes) { foreach (XmlNode c in n.ChildNodes) { switch (c.Name.ToLowerInvariant()) { case "ats": case "plugin": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of plugin sounds was defined in in XML file " + fileName); break; } if (!car.Specs.IsDriverCar) { break; } ParseArrayNode(c, out car.Sounds.Plugin, center, SoundCfgParser.mediumRadius); break; case "brake": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of brake sounds was defined in in XML file " + fileName); break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "releasehigh": //Release brakes from high pressure ParseNode(cc, out car.Sounds.AirHigh, center, SoundCfgParser.smallRadius); break; case "release": //Release brakes from normal pressure ParseNode(cc, out car.Sounds.Air, center, SoundCfgParser.smallRadius); break; case "releasefull": //Release brakes from full pressure ParseNode(cc, out car.Sounds.AirZero, center, SoundCfgParser.smallRadius); break; case "emergency": //Apply EB ParseNode(cc, out car.Sounds.EmrBrake, center, SoundCfgParser.smallRadius); break; case "application": //Standard application ParseNode(cc, out car.Sounds.Brake, center, SoundCfgParser.smallRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "brakehandle": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of brake handle sounds was defined in in XML file " + fileName); break; } if (!car.Specs.IsDriverCar) { break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "apply": ParseNode(cc, out car.Sounds.BrakeHandleApply, panel, SoundCfgParser.tinyRadius); break; case "release": ParseNode(cc, out car.Sounds.BrakeHandleRelease, panel, SoundCfgParser.tinyRadius); break; case "min": case "minimum": ParseNode(cc, out car.Sounds.BrakeHandleMin, panel, SoundCfgParser.tinyRadius); break; case "max": case "maximum": ParseNode(cc, out car.Sounds.BrakeHandleMax, panel, SoundCfgParser.tinyRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "breaker": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of breaker sounds was defined in in XML file " + fileName); break; } if (!car.Specs.IsDriverCar) { break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "on": ParseNode(cc, out car.Sounds.BreakerResume, panel, SoundCfgParser.smallRadius); break; case "off": ParseNode(cc, out car.Sounds.BreakerResumeOrInterrupt, panel, SoundCfgParser.smallRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "buzzer": if (!car.Specs.IsDriverCar) { break; } ParseNode(c, out car.Sounds.Adjust, panel, SoundCfgParser.tinyRadius); break; case "compressor": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of compressor sounds was defined in in XML file " + fileName); break; } if (car.Specs.AirBrake.Type != TrainManager.AirBrakeType.Main) { break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "attack": case "start": //Compressor starting sound ParseNode(cc, out car.Sounds.CpStart, center, SoundCfgParser.mediumRadius); break; case "loop": //Compressor loop sound ParseNode(cc, out car.Sounds.CpLoop, center, SoundCfgParser.mediumRadius); break; case "release": case "stop": case "end": //Compressor end sound ParseNode(cc, out car.Sounds.CpEnd, center, SoundCfgParser.mediumRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "door": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of door sounds was defined in in XML file " + fileName); break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "openleft": case "leftopen": ParseNode(cc, out car.Sounds.DoorOpenL, left, SoundCfgParser.smallRadius); break; case "openright": case "rightopen": ParseNode(cc, out car.Sounds.DoorOpenR, right, SoundCfgParser.smallRadius); break; case "closeleft": case "leftclose": ParseNode(cc, out car.Sounds.DoorCloseL, left, SoundCfgParser.smallRadius); break; case "closeright": case "rightclose": ParseNode(cc, out car.Sounds.DoorCloseR, right, SoundCfgParser.smallRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "flange": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of flange sounds was defined in in XML file " + fileName); break; } ParseArrayNode(c, out car.Sounds.Flange, center, SoundCfgParser.mediumRadius); break; case "horn": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of horn sounds was defined in in XML file " + fileName); break; } if (!car.Specs.IsDriverCar) { break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "primary": //Primary horn ParseHornNode(cc, out car.Sounds.Horns[0], front, SoundCfgParser.largeRadius); break; case "secondary": //Secondary horn ParseHornNode(cc, out car.Sounds.Horns[1], front, SoundCfgParser.largeRadius); break; case "music": //Music horn ParseHornNode(cc, out car.Sounds.Horns[2], front, SoundCfgParser.largeRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "loop": case "noise": ParseNode(c, out car.Sounds.Loop, center, SoundCfgParser.mediumRadius); break; case "mastercontroller": case "powerhandle": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of power handle sounds was defined in in XML file " + fileName); break; } if (!car.Specs.IsDriverCar) { break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "up": case "increase": ParseNode(cc, out car.Sounds.MasterControllerUp, panel, SoundCfgParser.tinyRadius); break; case "down": case "decrease": ParseNode(cc, out car.Sounds.MasterControllerDown, panel, SoundCfgParser.tinyRadius); break; case "min": case "minimum": ParseNode(cc, out car.Sounds.MasterControllerMin, panel, SoundCfgParser.tinyRadius); break; case "max": case "maximum": ParseNode(cc, out car.Sounds.MasterControllerMax, panel, SoundCfgParser.tinyRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "motor": if (!car.Specs.IsMotorCar) { break; } ParseMotorSoundTableNode(c, ref car.Sounds.Motor.Tables, center, SoundCfgParser.mediumRadius); break; case "pilotlamp": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of pilot-lamp sounds was defined in in XML file " + fileName); break; } if (!car.Specs.IsDriverCar) { break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "on": ParseNode(cc, out car.Sounds.PilotLampOn, panel, SoundCfgParser.tinyRadius); break; case "off": ParseNode(cc, out car.Sounds.PilotLampOff, panel, SoundCfgParser.tinyRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "pointfrontaxle": case "switchfrontaxle": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of point front axle sounds was defined in in XML file " + fileName); break; } ParseArrayNode(c, out car.Sounds.PointFrontAxle, new Vector3(0.0, 0.0, car.FrontAxle.Position), SoundCfgParser.smallRadius); break; case "pointrearaxle": case "switchrearaxle": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of point rear axle sounds was defined in in XML file " + fileName); break; } ParseArrayNode(c, out car.Sounds.PointRearAxle, new Vector3(0.0, 0.0, car.FrontAxle.Position), SoundCfgParser.smallRadius); break; case "reverser": case "reverserhandle": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of reverser sounds was defined in in XML file " + fileName); break; } if (!car.Specs.IsDriverCar) { break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "on": ParseNode(cc, out car.Sounds.ReverserOn, panel, SoundCfgParser.tinyRadius); break; case "off": ParseNode(cc, out car.Sounds.ReverserOff, panel, SoundCfgParser.tinyRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; case "run": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of run sounds was defined in in XML file " + fileName); break; } ParseArrayNode(c, out car.Sounds.Run, center, SoundCfgParser.mediumRadius); break; case "shoe": case "rub": ParseNode(c, out car.Sounds.Rub, center, SoundCfgParser.mediumRadius); break; case "suspension": case "spring": if (!c.HasChildNodes) { Interface.AddMessage(Interface.MessageType.Error, false, "An empty list of suspension sounds was defined in in XML file " + fileName); break; } foreach (XmlNode cc in c.ChildNodes) { switch (cc.Name.ToLowerInvariant()) { case "left": //Left suspension springs ParseNode(cc, out car.Sounds.SpringL, left, SoundCfgParser.smallRadius); break; case "right": //right suspension springs ParseNode(cc, out car.Sounds.SpringR, right, SoundCfgParser.smallRadius); break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node."); break; } } break; } } } } car.Sounds.RunVolume = new double[car.Sounds.Run.Length]; car.Sounds.FlangeVolume = new double[car.Sounds.FlangeVolume.Length]; } }
/// <summary>Updates the sound component. Should be called every frame.</summary> /// <param name="timeElapsed">The time in seconds that elapsed since the last call to this function.</param> private static void UpdateInverseModel(double timeElapsed) { /* * Set up the listener. * */ OpenBveApi.Math.Vector3 listenerPosition = World.AbsoluteCameraPosition; OpenBveApi.Math.Orientation3 listenerOrientation = new OpenBveApi.Math.Orientation3(World.AbsoluteCameraSide, World.AbsoluteCameraUp, World.AbsoluteCameraDirection); OpenBveApi.Math.Vector3 listenerVelocity; if (World.CameraMode == CameraViewMode.Interior | World.CameraMode == CameraViewMode.InteriorLookAhead | World.CameraMode == CameraViewMode.Exterior) { TrainManager.Car car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; OpenBveApi.Math.Vector3 diff = car.FrontAxle.Follower.WorldPosition - car.RearAxle.Follower.WorldPosition; if (diff.IsNullVector()) { listenerVelocity = car.Specs.CurrentSpeed * OpenBveApi.Math.Vector3.Forward; } else { listenerVelocity = car.Specs.CurrentSpeed * OpenBveApi.Math.Vector3.Normalize(diff); } } else { listenerVelocity = OpenBveApi.Math.Vector3.Zero; } AL.Listener(ALListener3f.Position, 0.0f, 0.0f, 0.0f); AL.Listener(ALListener3f.Velocity, (float)listenerVelocity.X, (float)listenerVelocity.Y, (float)listenerVelocity.Z); var Orientation = new float[] { (float)listenerOrientation.Z.X, (float)listenerOrientation.Z.Y, (float)listenerOrientation.Z.Z, -(float)listenerOrientation.Y.X, -(float)listenerOrientation.Y.Y, -(float)listenerOrientation.Y.Z }; AL.Listener(ALListenerfv.Orientation, ref Orientation); /* * Set up the atmospheric attributes. * */ double elevation = World.AbsoluteCameraPosition.Y + Game.RouteInitialElevation; double airTemperature = Game.GetAirTemperature(elevation); double airPressure = Game.GetAirPressure(elevation, airTemperature); double speedOfSound = Game.GetSpeedOfSound(airPressure, airTemperature); try { AL.SpeedOfSound((float)speedOfSound); } catch { } /* * Collect all sounds that are to be played * and ensure that all others are stopped. * */ List <SoundSourceAttenuation> toBePlayed = new List <SoundSourceAttenuation>(); for (int i = 0; i < SourceCount; i++) { if (Sources[i].State == SoundSourceState.StopPending) { /* * The sound is still playing but is to be stopped. * Stop the sound, then remove it from the list of * sound sources. * */ AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else if (Sources[i].State == SoundSourceState.Stopped) { /* * The sound was already stopped. Remove it from * the list of sound sources. * */ Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else if (GlobalMute) { /* * The sound is playing or about to be played, but * the global mute option is enabled. Stop the sound * sound if necessary, then remove it from the list * of sound sources if the sound is not looping. * */ if (Sources[i].State == SoundSourceState.Playing) { AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.PlayPending; Sources[i].OpenAlSourceName = 0; } if (!Sources[i].Looped) { Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } } else { /* * The sound is to be played or is already playing. * */ if (Sources[i].State == SoundSourceState.Playing) { int state; AL.GetSource(Sources[i].OpenAlSourceName, ALGetSourcei.SourceState, out state); if (state != (int)ALSourceState.Initial & state != (int)ALSourceState.Playing) { /* * The sound is not playing any longer. * Remove it from the list of sound sources. * */ AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; continue; } } /* * Calculate the gain, then add the sound * to the list of sounds to be played. * */ OpenBveApi.Math.Vector3 position; if (Sources[i].Train != null) { OpenBveApi.Math.Vector3 direction; Sources[i].Train.Cars[Sources[i].Car].CreateWorldCoordinates(Sources[i].Position, out position, out direction); } else { position = Sources[i].Position; } OpenBveApi.Math.Vector3 positionDifference = position - listenerPosition; double distance = positionDifference.Norm(); double radius = Sources[i].Radius; if (World.CameraMode == CameraViewMode.Interior | World.CameraMode == CameraViewMode.InteriorLookAhead) { if (Sources[i].Train != TrainManager.PlayerTrain || Sources[i].Car != TrainManager.PlayerTrain.DriverCar) { radius *= 0.5; } } double gain; if (distance < 2.0 * radius) { gain = 1.0 - distance * distance * (4.0 * radius - distance) / (16.0 * radius * radius * radius); } else { gain = radius / distance; } gain *= Sources[i].Volume; if (gain <= 0.0) { /* * The gain is too low. Stop the sound if playing, * but keep looping sounds pending. * */ if (Sources[i].State == SoundSourceState.Playing) { AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.PlayPending; Sources[i].OpenAlSourceName = 0; } if (!Sources[i].Looped) { Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } } else { /* * Add the source. * */ toBePlayed.Add(new SoundSourceAttenuation(Sources[i], gain, distance)); } } } /* * Now that we have the list of sounds that are to be played, * sort them by their gain so that we can determine and * adjust the clamp factor. * */ double clampFactor = Math.Exp(LogClampFactor); for (int i = 0; i < toBePlayed.Count; i++) { toBePlayed[i].Gain -= clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance; } toBePlayed.Sort(); for (int i = 0; i < toBePlayed.Count; i++) { toBePlayed[i].Gain += clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance; } double desiredLogClampFactor; int index = Interface.CurrentOptions.SoundNumber; if (toBePlayed.Count <= index) { desiredLogClampFactor = MinLogClampFactor; } else { double cutoffDistance = toBePlayed[index].Distance; if (cutoffDistance <= 0.0) { desiredLogClampFactor = MaxLogClampFactor; } else { double cutoffGain = toBePlayed[index].Gain; desiredLogClampFactor = Math.Log(cutoffGain / (cutoffDistance * cutoffDistance)); if (desiredLogClampFactor < MinLogClampFactor) { desiredLogClampFactor = MinLogClampFactor; } else if (desiredLogClampFactor > MaxLogClampFactor) { desiredLogClampFactor = MaxLogClampFactor; } } } const double rate = 3.0; if (LogClampFactor < desiredLogClampFactor) { LogClampFactor += timeElapsed * rate; if (LogClampFactor > desiredLogClampFactor) { LogClampFactor = desiredLogClampFactor; } } else if (LogClampFactor > desiredLogClampFactor) { LogClampFactor -= timeElapsed * rate; if (LogClampFactor < desiredLogClampFactor) { LogClampFactor = desiredLogClampFactor; } } /* * Play the sounds. * */ clampFactor = Math.Exp(LogClampFactor); for (int i = index; i < toBePlayed.Count; i++) { toBePlayed[i].Gain = 0.0; } for (int i = 0; i < toBePlayed.Count; i++) { SoundSource source = toBePlayed[i].Source; double gain = toBePlayed[i].Gain - clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance; if (gain <= 0.0) { /* * Stop the sound. * */ if (source.State == SoundSourceState.Playing) { AL.DeleteSources(1, ref source.OpenAlSourceName); source.State = SoundSourceState.PlayPending; source.OpenAlSourceName = 0; } if (!source.Looped) { source.State = SoundSourceState.Stopped; source.OpenAlSourceName = 0; } } else { /* * Ensure the buffer is loaded, then play the sound. * */ if (source.State != SoundSourceState.Playing) { LoadBuffer(source.Buffer); if (source.Buffer.Loaded) { AL.GenSources(1, out source.OpenAlSourceName); AL.Source(source.OpenAlSourceName, ALSourcei.Buffer, source.Buffer.OpenAlBufferName); } else { /* * We cannot play the sound because * the buffer could not be loaded. * */ source.State = SoundSourceState.Stopped; continue; } } OpenBveApi.Math.Vector3 position; OpenBveApi.Math.Vector3 velocity; if (source.Train != null) { OpenBveApi.Math.Vector3 direction; source.Train.Cars[source.Car].CreateWorldCoordinates(source.Position, out position, out direction); velocity = source.Train.Cars[source.Car].Specs.CurrentSpeed * direction; } else { position = source.Position; velocity = OpenBveApi.Math.Vector3.Zero; } position -= listenerPosition; AL.Source(source.OpenAlSourceName, ALSource3f.Position, (float)position.X, (float)position.Y, (float)position.Z); AL.Source(source.OpenAlSourceName, ALSource3f.Velocity, (float)velocity.X, (float)velocity.Y, (float)velocity.Z); AL.Source(source.OpenAlSourceName, ALSourcef.Pitch, (float)source.Pitch); AL.Source(source.OpenAlSourceName, ALSourcef.Gain, (float)gain); if (source.State != SoundSourceState.Playing) { AL.Source(source.OpenAlSourceName, ALSourceb.Looping, source.Looped); AL.SourcePlay(source.OpenAlSourceName); source.State = SoundSourceState.Playing; } } } }
/// <summary>Updates the sound component. Should be called every frame.</summary> /// <param name="timeElapsed">The time in seconds that elapsed since the last call to this function.</param> private static void UpdateLinearModel(double timeElapsed) { /* * Set up the listener * */ OpenBveApi.Math.Vector3D listenerPosition = World.AbsoluteCameraPosition; OpenBveApi.Math.Orientation3 listenerOrientation = new OpenBveApi.Math.Orientation3(World.AbsoluteCameraSide, World.AbsoluteCameraUp, World.AbsoluteCameraDirection); OpenBveApi.Math.Vector3D listenerVelocity; if (World.CameraMode == World.CameraViewMode.Interior || World.CameraMode == World.CameraViewMode.InteriorLookAhead || World.CameraMode == World.CameraViewMode.Exterior) { TrainManager.Car car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; OpenBveApi.Math.Vector3D diff = car.FrontAxle.Follower.WorldPosition - car.RearAxle.Follower.WorldPosition; listenerVelocity = car.Specs.CurrentSpeed * OpenBveApi.Math.Vector3D.Normalize(diff) + World.CameraAlignmentSpeed.Position; } else { listenerVelocity = World.CameraAlignmentSpeed.Position; } float[] vectors = { (float)listenerOrientation.Z.X, (float)listenerOrientation.Z.Y, (float)listenerOrientation.Z.Z, -(float)listenerOrientation.Y.X, -(float)listenerOrientation.Y.Y, -(float)listenerOrientation.Y.Z }; AL.Listener(ALListener3f.Position, 0.0f, 0.0f, 0.0f); AL.Listener(ALListener3f.Velocity, (float)listenerVelocity.X, (float)listenerVelocity.Y, (float)listenerVelocity.Z); AL.Listener(ALListenerfv.Orientation, ref vectors); /* * Set up the atmospheric attributes * */ double elevation = World.AbsoluteCameraPosition.Y + Game.RouteInitialElevation; double airTemperature = Game.GetAirTemperature(elevation); double airPressure = Game.GetAirPressure(elevation, airTemperature); // double airDensity = Game.GetAirDensity(airPressure, airTemperature); double speedOfSound = Game.GetSpeedOfSound(airPressure, airTemperature); try { AL.SpeedOfSound((float)speedOfSound); } catch { } /* * Update the sound sources * */ int actuallyPlaying = 0; for (int i = 0; i < SourceCount; i++) { if (Sources[i].State == SoundSourceState.StopPending) { /* * The sound is still playing but is to be stopped. * Stop the sound, then remove it from the list of * sound sources. * */ AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else if (Sources[i].State == SoundSourceState.Stopped) { /* * The sound was already stopped. Remove it from * the list of sound sources. * */ Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else if (GlobalMute) { /* * The sound is playing or about to be played, but * the global mute option is enabled. Stop the sound * sound if necessary, then remove it from the list * of sound sources if the sound is not looping. * */ if (Sources[i].State == SoundSourceState.Playing) { AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.PlayPending; Sources[i].OpenAlSourceName = 0; } if (!Sources[i].Looped) { Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } } else { /* * The sound is to be played or is already playing. * Calculate the sound gain. * */ OpenBveApi.Math.Vector3D position; OpenBveApi.Math.Vector3D velocity; if (Sources[i].Train != null) { OpenBveApi.Math.Vector3D direction; TrainManager.CreateWorldCoordinates(Sources[i].Train, Sources[i].Car, Sources[i].Position.X, Sources[i].Position.Y, Sources[i].Position.Z, out position.X, out position.Y, out position.Z, out direction.X, out direction.Y, out direction.Z); velocity = Sources[i].Train.Cars[Sources[i].Car].Specs.CurrentSpeed * direction; } else { position = Sources[i].Position; velocity = OpenBveApi.Math.Vector3D.Null; } OpenBveApi.Math.Vector3D positionDifference = position - listenerPosition; double gain; if (GlobalMute) { gain = 0.0; } else { double distance = positionDifference.Norm(); double innerRadius = Sources[i].Radius; if (World.CameraMode == World.CameraViewMode.Interior || World.CameraMode == World.CameraViewMode.InteriorLookAhead) { if (Sources[i].Train != TrainManager.PlayerTrain || Sources[i].Car != TrainManager.PlayerTrain.DriverCar) { innerRadius *= 0.5; } } double outerRadius = OuterRadiusFactor * innerRadius; if (distance < outerRadius) { if (distance <= innerRadius) { gain = Sources[i].Volume; } else { gain = (distance - outerRadius) / (innerRadius - outerRadius); gain *= Sources[i].Volume; } gain = 3.0 * gain * gain - 2.0 * gain * gain * gain; } else { gain = 0.0; } } if (gain <= GainThreshold) { /* * If the gain is too low to be audible, stop the sound. * If the sound is not looping, stop it if necessary, * then remove it from the list of sound sources. * */ if (Sources[i].State == SoundSourceState.Playing) { AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.PlayPending; Sources[i].OpenAlSourceName = 0; } if (!Sources[i].Looped) { Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } } else { /* * Play the sound and update position, velocity, pitch and gain. * For non-looping sounds, check if the sound is still playing. * */ gain = (gain - GainThreshold) / (1.0 - GainThreshold); if (Sources[i].State != SoundSourceState.Playing) { LoadBuffer(Sources[i].Buffer); if (Sources[i].Buffer.Loaded) { AL.GenSources(1, out Sources[i].OpenAlSourceName); AL.Source(Sources[i].OpenAlSourceName, ALSourcei.Buffer, Sources[i].Buffer.OpenAlBufferName); } else { /* * We cannot play the sound because * the buffer could not be loaded. * */ Sources[i].State = SoundSourceState.Stopped; continue; } } AL.Source(Sources[i].OpenAlSourceName, ALSource3f.Position, (float)positionDifference.X, (float)positionDifference.Y, (float)positionDifference.Z); AL.Source(Sources[i].OpenAlSourceName, ALSource3f.Velocity, (float)velocity.X, (float)velocity.Y, (float)velocity.Z); AL.Source(Sources[i].OpenAlSourceName, ALSourcef.Pitch, (float)Sources[i].Pitch); AL.Source(Sources[i].OpenAlSourceName, ALSourcef.Gain, (float)gain); if (Sources[i].State != SoundSourceState.Playing) { AL.Source(Sources[i].OpenAlSourceName, ALSourceb.Looping, Sources[i].Looped); AL.SourcePlay(Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Playing; } if (!Sources[i].Looped) { int state; AL.GetSource(Sources[i].OpenAlSourceName, ALGetSourcei.SourceState, out state); if (state != (int)ALSourceState.Initial && state != (int)ALSourceState.Playing) { /* * The sound is not playing any longer. * Remove it from the list of sound sources. * */ AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else { actuallyPlaying++; } } else { actuallyPlaying++; } } } } /* * Adjust the outer radius factor / the clamp factor. * */ if (actuallyPlaying >= Options.Current.SoundNumber - 2) { /* * Too many sounds are playing. * Reduce the outer radius factor. * */ OuterRadiusFactorSpeed -= timeElapsed; if (OuterRadiusFactorSpeed < -OuterRadiusFactorMaximumSpeed) { OuterRadiusFactorSpeed = -OuterRadiusFactorMaximumSpeed; } } else if (actuallyPlaying <= Options.Current.SoundNumber - 6) { /* * Only few sounds are playing. * Increase the outer radius factor. * */ OuterRadiusFactorSpeed += timeElapsed; if (OuterRadiusFactorSpeed > OuterRadiusFactorMaximumSpeed) { OuterRadiusFactorSpeed = OuterRadiusFactorMaximumSpeed; } } else { /* * Neither too many nor too few sounds are playing. * Stabilize the outer radius factor. * */ if (OuterRadiusFactorSpeed < 0.0) { OuterRadiusFactorSpeed += timeElapsed; if (OuterRadiusFactorSpeed > 0.0) { OuterRadiusFactorSpeed = 0.0; } } else { OuterRadiusFactorSpeed -= timeElapsed; if (OuterRadiusFactorSpeed < 0.0) { OuterRadiusFactorSpeed = 0.0; } } } OuterRadiusFactor += OuterRadiusFactorSpeed * timeElapsed; if (OuterRadiusFactor < OuterRadiusFactorMinimum) { OuterRadiusFactor = OuterRadiusFactorMinimum; OuterRadiusFactorSpeed = 0.0; } else if (OuterRadiusFactor > OuterRadiusFactorMaximum) { OuterRadiusFactor = OuterRadiusFactorMaximum; OuterRadiusFactorSpeed = 0.0; } }
internal static void LeaveCheck(Vector2 Point) { if (!Loading.SimulationSetup) { return; } if (World.CameraMode != CameraViewMode.Interior && World.CameraMode != CameraViewMode.InteriorLookAhead) { return; } TrainManager.Car Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; int add = Car.CarSections[0].CurrentAdditionalGroup + 1; if (add < Car.CarSections[0].Groups.Length) { TrainManager.TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; if (TouchElements != null) { foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; ShowObjectSelection(o); } int[] SelectBuffer = new int[2048]; PickPre(SelectBuffer, Point, new Vector2(5)); RenderSceneSelection(); int PickedObjectIndex = PickPost(SelectBuffer); foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; HideObjectSelection(o); if (o == PickedObjectIndex) { Car.CarSections[0].CurrentAdditionalGroup = TouchElement.JumpScreenIndex; Car.ChangeCarSection(TrainManager.CarSectionType.Interior); if (TouchElement.SoundIndex >= 0 && TouchElement.SoundIndex < Car.Sounds.Touch.Length) { Sounds.SoundBuffer Buffer = Car.Sounds.Touch[TouchElement.SoundIndex].Buffer; OpenBveApi.Math.Vector3 Position = Car.Sounds.Touch[TouchElement.SoundIndex].Position; Sounds.PlaySound(Buffer, 1.0, 1.0, Position, TrainManager.PlayerTrain, TrainManager.PlayerTrain.DriverCar, false); } } // HACK: Normally terminate the command issued once. if (o == PickedObjectIndex || (PickedObjectIndex != PrePickedObjectIndex && o == PrePickedObjectIndex)) { for (int i = 0; i < Interface.CurrentControls.Length; i++) { if (Interface.CurrentControls[i].Method != Interface.ControlMethod.Touch) { continue; } bool EnableOption = false; for (int j = 0; j < Translations.CommandInfos.Length; j++) { if (Interface.CurrentControls[i].Command == Translations.CommandInfos[j].Command) { EnableOption = Translations.CommandInfos[j].EnableOption; break; } } if (TouchElement.Command == Interface.CurrentControls[i].Command) { if (EnableOption && TouchElement.CommandOption != Interface.CurrentControls[i].Option) { continue; } Interface.CurrentControls[i].AnalogState = 0.0; Interface.CurrentControls[i].DigitalState = Interface.DigitalControlState.Released; MainLoop.RemoveControlRepeat(i); } } } } } } }
internal static void TouchCheck(Vector2 Point) { if (!Loading.SimulationSetup) { return; } if (World.CameraMode != CameraViewMode.Interior && World.CameraMode != CameraViewMode.InteriorLookAhead) { return; } TrainManager.Car Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; int add = Car.CarSections[0].CurrentAdditionalGroup + 1; if (add < Car.CarSections[0].Groups.Length) { TrainManager.TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; if (TouchElements != null) { foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; ShowObjectSelection(o); } int[] SelectBuffer = new int[2048]; PickPre(SelectBuffer, Point, new Vector2(5)); RenderSceneSelection(); int PickedObjectIndex = PickPost(SelectBuffer); foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; HideObjectSelection(o); if (o == PickedObjectIndex) { for (int i = 0; i < Interface.CurrentControls.Length; i++) { if (Interface.CurrentControls[i].Method != Interface.ControlMethod.Touch) { continue; } bool EnableOption = false; for (int j = 0; j < Translations.CommandInfos.Length; j++) { if (Interface.CurrentControls[i].Command == Translations.CommandInfos[j].Command) { EnableOption = Translations.CommandInfos[j].EnableOption; break; } } if (TouchElement.Command == Interface.CurrentControls[i].Command) { if (EnableOption && TouchElement.CommandOption != Interface.CurrentControls[i].Option) { continue; } Interface.CurrentControls[i].AnalogState = 1.0; Interface.CurrentControls[i].DigitalState = Interface.DigitalControlState.Pressed; MainLoop.AddControlRepeat(i); } } } } PrePickedObjectIndex = PickedObjectIndex; } } }
internal static bool MoveCheck(Vector2 Point, out Cursor.Status Status) { if (!Loading.SimulationSetup) { Status = Cursor.Status.Default; return(false); } if (World.CameraMode != CameraViewMode.Interior && World.CameraMode != CameraViewMode.InteriorLookAhead) { Status = Cursor.Status.Default; return(false); } Status = Cursor.Status.Default; TrainManager.Car Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; int add = Car.CarSections[0].CurrentAdditionalGroup + 1; if (add < Car.CarSections[0].Groups.Length) { TrainManager.TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; if (TouchElements != null) { foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; ShowObjectSelection(o); } int[] SelectBuffer = new int[2048]; PickPre(SelectBuffer, Point, new Vector2(5)); RenderSceneSelection(); int PickedObjectIndex = PickPost(SelectBuffer); foreach (var TouchElement in TouchElements) { int o = TouchElement.Element.ObjectIndex; HideObjectSelection(o); if (o == PickedObjectIndex) { switch (TouchElement.Command) { case Translations.Command.PowerIncrease: case Translations.Command.BrakeIncrease: case Translations.Command.ReverserForward: Status = Cursor.Status.Plus; break; case Translations.Command.PowerDecrease: case Translations.Command.BrakeDecrease: case Translations.Command.ReverserBackward: Status = Cursor.Status.Minus; break; } } } if (PickedObjectIndex >= 0) { return(true); } } } return(false); }
internal Axle(TrainManager.Train Train, TrainManager.Car Car) { Follower = new TrackFollower(Program.CurrentHost, Train, Car); }
/// <summary>Updates the sound component. Should be called every frame.</summary> /// <param name="timeElapsed">The time in seconds that elapsed since the last call to this function.</param> protected override void UpdateLinearModel(double timeElapsed) { /* * Set up the listener * */ Vector3 listenerPosition = Program.Renderer.Camera.AbsolutePosition; Orientation3 listenerOrientation = new Orientation3(Program.Renderer.Camera.AbsoluteSide, Program.Renderer.Camera.AbsoluteUp, Program.Renderer.Camera.AbsoluteDirection); Vector3 listenerVelocity; if (Program.Renderer.Camera.CurrentMode == CameraViewMode.Interior | Program.Renderer.Camera.CurrentMode == CameraViewMode.InteriorLookAhead | Program.Renderer.Camera.CurrentMode == CameraViewMode.Exterior) { TrainManager.Car car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; Vector3 diff = car.FrontAxle.Follower.WorldPosition - car.RearAxle.Follower.WorldPosition; listenerVelocity = car.CurrentSpeed * Vector3.Normalize(diff) + Program.Renderer.Camera.AlignmentSpeed.Position; } else { listenerVelocity = Program.Renderer.Camera.AlignmentSpeed.Position; } AL.Listener(ALListener3f.Position, 0.0f, 0.0f, 0.0f); AL.Listener(ALListener3f.Velocity, (float)listenerVelocity.X, (float)listenerVelocity.Y, (float)listenerVelocity.Z); var Orientation = new[] { (float)listenerOrientation.Z.X, (float)listenerOrientation.Z.Y, (float)listenerOrientation.Z.Z, -(float)listenerOrientation.Y.X, -(float)listenerOrientation.Y.Y, -(float)listenerOrientation.Y.Z }; AL.Listener(ALListenerfv.Orientation, ref Orientation); /* * Set up the atmospheric attributes * */ double elevation = Program.Renderer.Camera.AbsolutePosition.Y + Program.CurrentRoute.Atmosphere.InitialElevation; double airTemperature = Program.CurrentRoute.Atmosphere.GetAirTemperature(elevation); double airPressure = Program.CurrentRoute.Atmosphere.GetAirPressure(elevation, airTemperature); double speedOfSound = Program.CurrentRoute.Atmosphere.GetSpeedOfSound(airPressure, airTemperature); try { AL.SpeedOfSound((float)speedOfSound); } catch { } /* * Update the sound sources * */ int actuallyPlaying = 0; for (int i = 0; i < SourceCount; i++) { if (Sources[i].State == SoundSourceState.StopPending) { /* * The sound is still playing but is to be stopped. * Stop the sound, then remove it from the list of * sound sources. * */ AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else if (Sources[i].State == SoundSourceState.Stopped) { /* * The sound was already stopped. Remove it from * the list of sound sources. * */ Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else if (GlobalMute) { /* * The sound is playing or about to be played, but * the global mute option is enabled. Stop the sound * sound if necessary, then remove it from the list * of sound sources if the sound is not looping. * */ if (Sources[i].State == SoundSourceState.Playing) { AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.PlayPending; Sources[i].OpenAlSourceName = 0; } if (!Sources[i].Looped) { Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } } else { /* * The sound is to be played or is already playing. * Calculate the sound gain. * */ Vector3 position; Vector3 velocity; switch (Sources[i].Type) { case SoundType.TrainCar: Vector3 direction; //only relevant for moving objects var Car = (AbstractCar)Sources[i].Parent; Car.CreateWorldCoordinates(Sources[i].Position, out position, out direction); velocity = Car.CurrentSpeed * direction; break; case SoundType.AnimatedObject: var WorldSound = (WorldSound)Sources[i].Parent; //TODO: Calculate speed... position = WorldSound.Follower.WorldPosition + WorldSound.Position; velocity = Vector3.Zero; break; default: position = Sources[i].Position; velocity = Vector3.Zero; break; } Vector3 positionDifference = position - listenerPosition; double gain; if (GlobalMute) { gain = 0.0; } else { double distance = positionDifference.Norm(); double innerRadius = Sources[i].Radius; if (Program.Renderer.Camera.CurrentMode == CameraViewMode.Interior | Program.Renderer.Camera.CurrentMode == CameraViewMode.InteriorLookAhead) { if (Sources[i].Parent != TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]) { innerRadius *= 0.5; } } double outerRadius = OuterRadiusFactor * innerRadius; if (distance < outerRadius) { if (distance <= innerRadius) { gain = Sources[i].Volume; } else { gain = (distance - outerRadius) / (innerRadius - outerRadius); gain *= Sources[i].Volume; } gain = 3.0 * gain * gain - 2.0 * gain * gain * gain; } else { gain = 0.0; } } if (gain <= GainThreshold) { /* * If the gain is too low to be audible, stop the sound. * If the sound is not looping, stop it if necessary, * then remove it from the list of sound sources. * */ if (Sources[i].State == SoundSourceState.Playing) { AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.PlayPending; Sources[i].OpenAlSourceName = 0; } if (!Sources[i].Looped) { Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } } else { /* * Play the sound and update position, velocity, pitch and gain. * For non-looping sounds, check if the sound is still playing. * */ gain = (gain - GainThreshold) / (1.0 - GainThreshold); if (Sources[i].State != SoundSourceState.Playing) { LoadBuffer(Sources[i].Buffer); if (Sources[i].Buffer.Loaded) { AL.GenSources(1, out Sources[i].OpenAlSourceName); AL.Source(Sources[i].OpenAlSourceName, ALSourcei.Buffer, Sources[i].Buffer.OpenAlBufferName); } else { /* * We cannot play the sound because * the buffer could not be loaded. * */ Sources[i].State = SoundSourceState.Stopped; continue; } } AL.Source(Sources[i].OpenAlSourceName, ALSource3f.Position, (float)positionDifference.X, (float)positionDifference.Y, (float)positionDifference.Z); AL.Source(Sources[i].OpenAlSourceName, ALSource3f.Velocity, (float)velocity.X, (float)velocity.Y, (float)velocity.Z); AL.Source(Sources[i].OpenAlSourceName, ALSourcef.Pitch, (float)Sources[i].Pitch); AL.Source(Sources[i].OpenAlSourceName, ALSourcef.Gain, (float)gain); if (Sources[i].State != SoundSourceState.Playing) { AL.Source(Sources[i].OpenAlSourceName, ALSourceb.Looping, Sources[i].Looped); AL.SourcePlay(Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Playing; } if (!Sources[i].Looped) { int state; AL.GetSource(Sources[i].OpenAlSourceName, ALGetSourcei.SourceState, out state); if (state != (int)ALSourceState.Initial & state != (int)ALSourceState.Playing) { /* * The sound is not playing any longer. * Remove it from the list of sound sources. * */ AL.DeleteSources(1, ref Sources[i].OpenAlSourceName); Sources[i].State = SoundSourceState.Stopped; Sources[i].OpenAlSourceName = 0; Sources[i] = Sources[SourceCount - 1]; SourceCount--; i--; } else { actuallyPlaying++; } } else { actuallyPlaying++; } } } } /* * Adjust the outer radius factor / the clamp factor. * */ if (actuallyPlaying >= Interface.CurrentOptions.SoundNumber - 2) { /* * Too many sounds are playing. * Reduce the outer radius factor. * */ OuterRadiusFactorSpeed -= timeElapsed; if (OuterRadiusFactorSpeed < -OuterRadiusFactorMaximumSpeed) { OuterRadiusFactorSpeed = -OuterRadiusFactorMaximumSpeed; } } else if (actuallyPlaying <= Interface.CurrentOptions.SoundNumber - 6) { /* * Only few sounds are playing. * Increase the outer radius factor. * */ OuterRadiusFactorSpeed += timeElapsed; if (OuterRadiusFactorSpeed > OuterRadiusFactorMaximumSpeed) { OuterRadiusFactorSpeed = OuterRadiusFactorMaximumSpeed; } } else { /* * Neither too many nor too few sounds are playing. * Stabilize the outer radius factor. * */ if (OuterRadiusFactorSpeed < 0.0) { OuterRadiusFactorSpeed += timeElapsed; if (OuterRadiusFactorSpeed > 0.0) { OuterRadiusFactorSpeed = 0.0; } } else { OuterRadiusFactorSpeed -= timeElapsed; if (OuterRadiusFactorSpeed < 0.0) { OuterRadiusFactorSpeed = 0.0; } } } OuterRadiusFactor += OuterRadiusFactorSpeed * timeElapsed; if (OuterRadiusFactor < OuterRadiusFactorMinimum) { OuterRadiusFactor = OuterRadiusFactorMinimum; OuterRadiusFactorSpeed = 0.0; } else if (OuterRadiusFactor > OuterRadiusFactorMaximum) { OuterRadiusFactor = OuterRadiusFactorMaximum; OuterRadiusFactorSpeed = 0.0; } RecAndPlay(listenerPosition, true, 0.0); }