1
0

SharedLightCycleSystem.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. using Content.Shared.Light.Components;
  2. using Robust.Shared.Map.Components;
  3. namespace Content.Shared.Light.EntitySystems;
  4. public abstract class SharedLightCycleSystem : EntitySystem
  5. {
  6. public override void Initialize()
  7. {
  8. base.Initialize();
  9. SubscribeLocalEvent<LightCycleComponent, MapInitEvent>(OnCycleMapInit);
  10. SubscribeLocalEvent<LightCycleComponent, ComponentShutdown>(OnCycleShutdown);
  11. }
  12. protected virtual void OnCycleMapInit(Entity<LightCycleComponent> ent, ref MapInitEvent args)
  13. {
  14. if (TryComp(ent.Owner, out MapLightComponent? mapLight))
  15. {
  16. ent.Comp.OriginalColor = mapLight.AmbientLightColor;
  17. Dirty(ent);
  18. }
  19. }
  20. private void OnCycleShutdown(Entity<LightCycleComponent> ent, ref ComponentShutdown args)
  21. {
  22. if (TryComp(ent.Owner, out MapLightComponent? mapLight))
  23. {
  24. mapLight.AmbientLightColor = ent.Comp.OriginalColor;
  25. Dirty(ent.Owner, mapLight);
  26. }
  27. }
  28. public void SetOffset(Entity<LightCycleComponent> entity, TimeSpan offset)
  29. {
  30. entity.Comp.Offset = offset;
  31. var ev = new LightCycleOffsetEvent(offset);
  32. RaiseLocalEvent(entity, ref ev);
  33. Dirty(entity);
  34. }
  35. public static Color GetColor(Entity<LightCycleComponent> cycle, Color color, float time)
  36. {
  37. if (cycle.Comp.Enabled)
  38. {
  39. var lightLevel = CalculateLightLevel(cycle.Comp, time);
  40. var colorLevel = CalculateColorLevel(cycle.Comp, time);
  41. return new Color(
  42. (byte)Math.Min(255, color.RByte * colorLevel.R * lightLevel),
  43. (byte)Math.Min(255, color.GByte * colorLevel.G * lightLevel),
  44. (byte)Math.Min(255, color.BByte * colorLevel.B * lightLevel)
  45. );
  46. }
  47. return color;
  48. }
  49. /// <summary>
  50. /// Calculates light intensity as a function of time.
  51. /// </summary>
  52. public static double CalculateLightLevel(LightCycleComponent comp, float time)
  53. {
  54. var waveLength = MathF.Max(1, (float) comp.Duration.TotalSeconds);
  55. var crest = MathF.Max(0f, comp.MaxLightLevel);
  56. var shift = MathF.Max(0f, comp.MinLightLevel);
  57. return Math.Min(comp.ClipLight, CalculateCurve(time, waveLength, crest, shift, 6));
  58. }
  59. /// <summary>
  60. /// It is important to note that each color must have a different exponent, to modify how early or late one color should stand out in relation to another.
  61. /// This "simulates" what the atmosphere does and is what generates the effect of dawn and dusk.
  62. /// The blue component must be a cosine function with half period, so that its minimum is at dawn and dusk, generating the "warm" color corresponding to these periods.
  63. /// As you can see in the values, the maximums of the function serve more to define the curve behavior,
  64. /// they must be "clipped" so as not to distort the original color of the lighting. In practice, the maximum values, in fact, are the clip thresholds.
  65. /// </summary>
  66. public static Color CalculateColorLevel(LightCycleComponent comp, float time)
  67. {
  68. var waveLength = MathF.Max(1f, (float) comp.Duration.TotalSeconds);
  69. var red = MathF.Min(comp.ClipLevel.R,
  70. CalculateCurve(time,
  71. waveLength,
  72. MathF.Max(0f, comp.MaxLevel.R),
  73. MathF.Max(0f, comp.MinLevel.R),
  74. 4f));
  75. var green = MathF.Min(comp.ClipLevel.G,
  76. CalculateCurve(time,
  77. waveLength,
  78. MathF.Max(0f, comp.MaxLevel.G),
  79. MathF.Max(0f, comp.MinLevel.G),
  80. 10f));
  81. var blue = MathF.Min(comp.ClipLevel.B,
  82. CalculateCurve(time,
  83. waveLength / 2f,
  84. MathF.Max(0f, comp.MaxLevel.B),
  85. MathF.Max(0f, comp.MinLevel.B),
  86. 2,
  87. waveLength / 4f));
  88. return new Color(red, green, blue);
  89. }
  90. /// <summary>
  91. /// Generates a sinusoidal curve as a function of x (time). The other parameters serve to adjust the behavior of the curve.
  92. /// </summary>
  93. /// <param name="x"> It corresponds to the independent variable of the function, which in the context of this algorithm is the current time. </param>
  94. /// <param name="waveLength"> It's the wavelength of the function, it can be said to be the total duration of the light cycle. </param>
  95. /// <param name="crest"> It's the maximum point of the function, where it will have its greatest value. </param>
  96. /// <param name="shift"> It's the vertical displacement of the function, in practice it corresponds to the minimum value of the function. </param>
  97. /// <param name="exponent"> It is the exponent of the sine, serves to "flatten" the function close to its minimum points and make it "steeper" close to its maximum. </param>
  98. /// <param name="phase"> It changes the phase of the wave, like a "horizontal shift". It is important to transform the sinusoidal function into cosine, when necessary. </param>
  99. /// <returns> The result of the function. </returns>
  100. public static float CalculateCurve(float x,
  101. float waveLength,
  102. float crest,
  103. float shift,
  104. float exponent,
  105. float phase = 0)
  106. {
  107. var sen = MathF.Pow(MathF.Sin((MathF.PI * (phase + x)) / waveLength), exponent);
  108. return (crest - shift) * sen + shift;
  109. }
  110. }
  111. /// <summary>
  112. /// Raised when the offset on <see cref="LightCycleComponent"/> changes.
  113. /// </summary>
  114. [ByRefEvent]
  115. public record struct LightCycleOffsetEvent(TimeSpan Offset)
  116. {
  117. public readonly TimeSpan Offset = Offset;
  118. }