Client LuaCsForBarotrauma
LightManager.cs
1 using Microsoft.Xna.Framework;
2 using Microsoft.Xna.Framework.Graphics;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System;
8 using System.Threading;
9 
10 namespace Barotrauma.Lights
11 {
13  {
18  const int MaxLightVolumeRecalculationsPerFrame = 5;
19 
24  const float ObstructLightsBehindCharactersZoomThreshold = 0.5f;
25 
26  private Thread rayCastThread;
27  private Queue<RayCastTask> pendingRayCasts = new Queue<RayCastTask>();
28 
29  public static Entity ViewTarget { get; set; }
30 
31  private float currLightMapScale;
32 
33  public Color AmbientLight;
34 
35  public RenderTarget2D LightMap
36  {
37  get;
38  private set;
39  }
40  public RenderTarget2D LimbLightMap
41  {
42  get;
43  private set;
44  }
45  public RenderTarget2D LosTexture
46  {
47  get;
48  private set;
49  }
50  public RenderTarget2D HighlightMap
51  {
52  get;
53  private set;
54  }
55 
56  private readonly Texture2D highlightRaster;
57 
58  private BasicEffect lightEffect;
59 
60  public Effect LosEffect { get; private set; }
61  public Effect SolidColorEffect { get; private set; }
62 
63  private readonly List<LightSource> lights;
64 
65  public bool DebugLos;
66 
67  public bool LosEnabled = true;
68  public float LosAlpha = 1f;
69  public LosMode LosMode = LosMode.Transparent;
70 
71  public bool LightingEnabled = true;
72 
73  public float ObstructVisionAmount;
74 
75  private readonly Texture2D visionCircle;
76 
77  private readonly Texture2D gapGlowTexture;
78 
79  private Vector2 losOffset;
80 
81  private int recalculationCount;
82 
83  private float time;
84 
85  public IEnumerable<LightSource> Lights
86  {
87  get { return lights; }
88  }
89 
90  public LightManager(GraphicsDevice graphics)
91  {
92  lights = new List<LightSource>(100);
93 
94  AmbientLight = new Color(20, 20, 20, 255);
95 
96  rayCastThread = new Thread(UpdateRayCasts)
97  {
98  Name = "LightManager Raycast thread",
99  IsBackground = true //this should kill the thread if the game crashes
100  };
101  rayCastThread.Start();
102 
103  visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
104  highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png");
105  gapGlowTexture = Sprite.LoadTexture("Content/Lights/pointlight_rays.png");
106 
108  {
109  CreateRenderTargets(graphics);
110  };
111 
112  CrossThread.RequestExecutionOnMainThread(() =>
113  {
114  CreateRenderTargets(graphics);
115 
116  LosEffect = EffectLoader.Load("Effects/losshader");
117  SolidColorEffect = EffectLoader.Load("Effects/solidcolor");
118 
119  lightEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
120  {
121  VertexColorEnabled = true,
122  TextureEnabled = true,
123  Texture = LightSource.LightTexture
124  };
125  });
126  }
127 
128  private void CreateRenderTargets(GraphicsDevice graphics)
129  {
130  var pp = graphics.PresentationParameters;
131 
132  currLightMapScale = GameSettings.CurrentConfig.Graphics.LightMapScale;
133 
134  LightMap?.Dispose();
135  LightMap = CreateRenderTarget();
136 
137  LimbLightMap?.Dispose();
138  LimbLightMap = CreateRenderTarget();
139 
140  HighlightMap?.Dispose();
141  HighlightMap = CreateRenderTarget();
142 
143  RenderTarget2D CreateRenderTarget()
144  {
145  return new RenderTarget2D(graphics,
146  (int)(GameMain.GraphicsWidth * GameSettings.CurrentConfig.Graphics.LightMapScale), (int)(GameMain.GraphicsHeight * GameSettings.CurrentConfig.Graphics.LightMapScale), false,
147  pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount,
148  RenderTargetUsage.DiscardContents);
149  }
150 
151  LosTexture?.Dispose();
152  LosTexture = new RenderTarget2D(graphics,
153  (int)(GameMain.GraphicsWidth * GameSettings.CurrentConfig.Graphics.LightMapScale),
154  (int)(GameMain.GraphicsHeight * GameSettings.CurrentConfig.Graphics.LightMapScale), false, SurfaceFormat.Color, DepthFormat.None);
155  }
156 
157  public void AddLight(LightSource light)
158  {
159  if (!lights.Contains(light)) { lights.Add(light); }
160  }
161 
162  public void RemoveLight(LightSource light)
163  {
164  lights.Remove(light);
165  }
166 
167  public void OnMapLoaded()
168  {
169  foreach (LightSource light in lights)
170  {
171  light.HullsUpToDate.Clear();
172  light.NeedsRecalculation = true;
173  }
174  }
175 
176  private readonly List<LightSource> activeLights = new List<LightSource>(capacity: 100);
177  private readonly List<LightSource> activeLightsWithLightVolume = new List<LightSource>(capacity: 100);
178 
179  public static int ActiveLightCount { get; private set; }
180 
181  public void Update(float deltaTime)
182  {
183  //wrap around if the timer gets very large, otherwise we'd start running into floating point accuracy issues
184  time = (time + deltaTime) % 100000.0f;
185  foreach (LightSource light in activeLights)
186  {
187  if (!light.Enabled) { continue; }
188  light.Update(time);
189  }
190  }
191 
192  private sealed class RayCastTask
193  {
194  public LightSource LightSource;
195  public Vector2 DrawPos;
196  public float Rotation;
197 
198  public RayCastTask(LightSource lightSource, Vector2 drawPos, float rotation)
199  {
200  LightSource = lightSource;
201  DrawPos = drawPos;
202  Rotation = rotation;
203  }
204 
205  public void Calculate()
206  {
207  LightSource.RayCastTask(DrawPos, Rotation);
208  }
209  }
210 
211  private static readonly object mutex = new object();
212 
213  public void AddRayCastTask(LightSource lightSource, Vector2 drawPos, float rotation)
214  {
215  lock (mutex)
216  {
217  if (pendingRayCasts.Any(p => p.LightSource == lightSource)) { return; }
218  pendingRayCasts.Enqueue(new RayCastTask(lightSource, drawPos, rotation));
219  }
220  }
221 
222  private void UpdateRayCasts()
223  {
224  while (true)
225  {
226  lock (mutex)
227  {
228  while (pendingRayCasts.Count > 0)
229  {
230  pendingRayCasts.Dequeue().Calculate();
231  }
232  }
233  Thread.Sleep(10);
234  }
235  }
236 
237  public void DebugDrawVertices(SpriteBatch spriteBatch)
238  {
239  foreach (LightSource light in lights)
240  {
241  if (!light.Enabled) { continue; }
242  light.DebugDrawVertices(spriteBatch);
243  }
244  }
245 
246  public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
247  {
248  if (!LightingEnabled) { return; }
249 
250  if (Math.Abs(currLightMapScale - GameSettings.CurrentConfig.Graphics.LightMapScale) > 0.01f)
251  {
252  //lightmap scale has changed -> recreate render targets
253  CreateRenderTargets(graphics);
254  }
255 
256  Matrix spriteBatchTransform = cam.Transform * Matrix.CreateScale(new Vector3(GameSettings.CurrentConfig.Graphics.LightMapScale, GameSettings.CurrentConfig.Graphics.LightMapScale, 1.0f));
257  Matrix transform = cam.ShaderTransform
258  * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
259 
260  bool highlightsVisible = UpdateHighlights(graphics, spriteBatch, spriteBatchTransform, cam);
261 
262  Rectangle viewRect = cam.WorldView;
263  viewRect.Y -= cam.WorldView.Height;
264  //check which lights need to be drawn
265  recalculationCount = 0;
266  activeLights.Clear();
267  foreach (LightSource light in lights)
268  {
269  if (!light.Enabled) { continue; }
270  if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; }
271 
272  if (light.ParentBody != null)
273  {
275 
276  Vector2 pos = light.ParentBody.DrawPosition;
277  if (light.ParentSub != null) { pos -= light.ParentSub.DrawPosition; }
278  light.Position = pos;
279  }
280 
281  //above the top boundary of the level (in an inactive respawn shuttle?)
282  if (Level.Loaded != null && light.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
283 
284  float range = light.LightSourceParams.TextureRange;
285  if (light.LightSprite != null)
286  {
287  float spriteRange = Math.Max(
288  light.LightSprite.size.X * light.SpriteScale.X * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.X - 0.5f)),
289  light.LightSprite.size.Y * light.SpriteScale.Y * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.Y - 0.5f)));
290 
291  float targetSize = Math.Max(light.LightTextureTargetSize.X, light.LightTextureTargetSize.Y);
292  range = Math.Max(Math.Max(spriteRange, targetSize), range);
293  }
294  if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; }
295 
296  light.Priority = lightPriority(range, light);
297 
298  int i = 0;
299  while (i < activeLights.Count && light.Priority < activeLights[i].Priority)
300  {
301  i++;
302  }
303  activeLights.Insert(i, light);
304  }
305  ActiveLightCount = activeLights.Count;
306 
307  float lightPriority(float range, LightSource light)
308  {
309  return
310  range *
311  ((Character.Controlled?.Submarine != null && light.ParentSub == Character.Controlled?.Submarine) ? 2.0f : 1.0f) *
312  (light.CastShadows ? 10.0f : 1.0f) *
313  (light.LightSourceParams.OverrideLightSpriteAlpha ?? (light.Color.A / 255.0f)) *
314  light.PriorityMultiplier;
315  }
316 
317  //find the lights with an active light volume
318  activeLightsWithLightVolume.Clear();
319  foreach (var activeLight in activeLights)
320  {
321  if (activeLight.Range < 1.0f || activeLight.Color.A < 1 || activeLight.CurrentBrightness <= 0.0f) { continue; }
322  activeLightsWithLightVolume.Add(activeLight);
323  }
324 
325  //remove some lights with a light volume if there's too many of them
326  if (activeLightsWithLightVolume.Count > GameSettings.CurrentConfig.Graphics.VisibleLightLimit && Screen.Selected is { IsEditor: false })
327  {
328  for (int i = GameSettings.CurrentConfig.Graphics.VisibleLightLimit; i < activeLightsWithLightVolume.Count; i++)
329  {
330  activeLights.Remove(activeLightsWithLightVolume[i]);
331  }
332  }
333  activeLights.Sort((l1, l2) => l1.LastRecalculationTime.CompareTo(l2.LastRecalculationTime));
334 
335  //draw light sprites attached to characters
336  //render into a separate rendertarget using alpha blending (instead of on top of everything else with alpha blending)
337  //to prevent the lights from showing through other characters or other light sprites attached to the same character
338  //---------------------------------------------------------------------------------------------------
339  graphics.SetRenderTarget(LimbLightMap);
340  graphics.Clear(Color.Black);
341  graphics.BlendState = BlendState.NonPremultiplied;
342  spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
343  foreach (LightSource light in activeLights)
344  {
345  if (light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
346  //draw limb lights at this point, because they were skipped over previously to prevent them from being obstructed
347  if (light.ParentBody?.UserData is Limb limb && !limb.Hide) { light.DrawSprite(spriteBatch, cam); }
348  }
349  spriteBatch.End();
350 
351  //draw background lights
352  //---------------------------------------------------------------------------------------------------
353  graphics.SetRenderTarget(LightMap);
354  graphics.Clear(AmbientLight);
355  graphics.BlendState = BlendState.Additive;
356  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
357  Level.Loaded?.BackgroundCreatureManager?.DrawLights(spriteBatch, cam);
358  foreach (LightSource light in activeLights)
359  {
360  if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
361  light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
362  light.DrawSprite(spriteBatch, cam);
363  }
364  GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
365  spriteBatch.End();
366 
367  //draw a black rectangle on hulls to hide background lights behind subs
368  //---------------------------------------------------------------------------------------------------
369 
370  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform);
371  Dictionary<Hull, Rectangle> visibleHulls = GetVisibleHulls(cam);
372  foreach (KeyValuePair<Hull, Rectangle> hull in visibleHulls)
373  {
374  GUI.DrawRectangle(spriteBatch,
375  new Vector2(hull.Value.X, -hull.Value.Y),
376  new Vector2(hull.Value.Width, hull.Value.Height),
377  hull.Key.AmbientLight == Color.TransparentBlack ? Color.Black : hull.Key.AmbientLight.Multiply(hull.Key.AmbientLight.A / 255.0f), true);
378  }
379  spriteBatch.End();
380 
381  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
382  Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight);
383  glowColorHSV.Z = Math.Max(glowColorHSV.Z, 0.4f);
384  Color glowColor = ToolBoxCore.HSVToRGB(glowColorHSV.X, glowColorHSV.Y, glowColorHSV.Z);
385  Vector2 glowSpriteSize = new Vector2(gapGlowTexture.Width, gapGlowTexture.Height);
386  foreach (var gap in Gap.GapList)
387  {
388  if (gap.IsRoomToRoom || gap.Open <= 0.0f || gap.ConnectedWall == null) { continue; }
389 
390  float a = MathHelper.Lerp(0.5f, 1.0f,
391  PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.05f, gap.GlowEffectT));
392 
393  float scale = MathHelper.Lerp(0.5f, 2.0f,
394  PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.01f, gap.GlowEffectT));
395 
396  float rot = PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.001f, gap.GlowEffectT) * MathHelper.TwoPi;
397 
398  Vector2 spriteScale = new Vector2(gap.Rect.Width, gap.Rect.Height) / glowSpriteSize;
399  Vector2 drawPos = new Vector2(gap.DrawPosition.X, -gap.DrawPosition.Y);
400 
401  spriteBatch.Draw(gapGlowTexture,
402  drawPos,
403  null,
404  glowColor * a,
405  rot,
406  glowSpriteSize / 2,
407  scale: Math.Max(spriteScale.X, spriteScale.Y) * scale,
408  SpriteEffects.None,
409  layerDepth: 0);
410  }
411  spriteBatch.End();
412 
413  GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
414  GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
415  spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
416  Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
417  spriteBatch.End();
418 
419  graphics.BlendState = BlendState.Additive;
420 
421  //draw the focused item and character to highlight them,
422  //and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites)
423  //---------------------------------------------------------------------------------------------------
424  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
425  foreach (LightSource light in activeLights)
426  {
427  //don't draw limb lights at this point, they need to be drawn after lights have been obstructed by characters
428  if (light.IsBackground || light.ParentBody?.UserData is Limb || light.CurrentBrightness <= 0.0f) { continue; }
429  light.DrawSprite(spriteBatch, cam);
430  }
431  spriteBatch.End();
432 
433  if (highlightsVisible)
434  {
435  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
436  spriteBatch.Draw(HighlightMap, Vector2.Zero, Color.White);
437  spriteBatch.End();
438  }
439 
440  //draw characters to obstruct the highlighted items/characters and light sprites
441  //---------------------------------------------------------------------------------------------------
442  if (cam.Zoom > ObstructLightsBehindCharactersZoomThreshold)
443  {
444  SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"];
445  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
446  DrawCharacters(spriteBatch, cam, drawDeformSprites: false);
447  spriteBatch.End();
448 
449  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"];
450  DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
451  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
452  DrawCharacters(spriteBatch, cam, drawDeformSprites: true);
453  spriteBatch.End();
454  }
455 
456  static void DrawCharacters(SpriteBatch spriteBatch, Camera cam, bool drawDeformSprites)
457  {
458  foreach (Character character in Character.CharacterList)
459  {
460  if (character.CurrentHull == null || !character.Enabled || !character.IsVisible || character.InvisibleTimer > 0.0f) { continue; }
461  if (Character.Controlled?.FocusedCharacter == character) { continue; }
462  Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
463  Color.Black :
464  character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
465  foreach (Limb limb in character.AnimController.Limbs)
466  {
467  if (drawDeformSprites == (limb.DeformSprite == null)) { continue; }
468  limb.Draw(spriteBatch, cam, lightColor);
469  }
470  foreach (var heldItem in character.HeldItems)
471  {
472  heldItem.Draw(spriteBatch, editing: false, overrideColor: Color.Black);
473  }
474  }
475  }
476 
477  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
478  graphics.BlendState = BlendState.Additive;
479 
480  //draw the actual light volumes, additive particles, hull ambient lights and the halo around the player
481  //---------------------------------------------------------------------------------------------------
482  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
483 
484  spriteBatch.Draw(LimbLightMap, new Rectangle(cam.WorldView.X, -cam.WorldView.Y, cam.WorldView.Width, cam.WorldView.Height), Color.White);
485 
486  foreach (ElectricalDischarger discharger in ElectricalDischarger.List)
487  {
488  discharger.DrawElectricity(spriteBatch);
489  }
490 
491  foreach (LightSource light in activeLights)
492  {
493  if (light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
494  light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
495  }
496 
498  {
499  foreach (MapEntity e in (Submarine.VisibleEntities ?? MapEntity.MapEntityList))
500  {
501  if (e is Item item && !item.IsHidden && item.GetComponent<Wire>() is Wire wire)
502  {
503  wire.DebugDraw(spriteBatch, alpha: 0.4f);
504  }
505  }
506  }
507 
508  lightEffect.World = transform;
509 
510  GameMain.ParticleManager.Draw(spriteBatch, false, null, Particles.ParticleBlendState.Additive);
511 
512  if (Character.Controlled != null)
513  {
514  DrawHalo(Character.Controlled);
515  }
516  else
517  {
518  foreach (Character character in Character.CharacterList)
519  {
520  if (character.Submarine == null || character.IsDead || !character.IsHuman) { continue; }
521  DrawHalo(character);
522  }
523  }
524 
525  void DrawHalo(Character character)
526  {
527  if (character == null || character.Removed) { return; }
528  Vector2 haloDrawPos = character.DrawPosition;
529  haloDrawPos.Y = -haloDrawPos.Y;
530 
531  //ambient light decreases the brightness of the halo (no need for a bright halo if the ambient light is bright enough)
532  float ambientBrightness = (AmbientLight.R + AmbientLight.B + AmbientLight.G) / 255.0f / 3.0f;
533  Color haloColor = Color.White.Multiply(0.3f - ambientBrightness);
534  if (haloColor.A > 0)
535  {
536  float scale = 512.0f / LightSource.LightTexture.Width;
537  spriteBatch.Draw(
538  LightSource.LightTexture, haloDrawPos, null, haloColor, 0.0f,
539  new Vector2(LightSource.LightTexture.Width, LightSource.LightTexture.Height) / 2, scale, SpriteEffects.None, 0.0f);
540  }
541  }
542 
543  spriteBatch.End();
544 
545  //draw the actual light volumes, additive particles, hull ambient lights and the halo around the player
546  //---------------------------------------------------------------------------------------------------
547 
548  graphics.SetRenderTarget(null);
549  graphics.BlendState = BlendState.NonPremultiplied;
550  }
551 
552  private readonly List<Entity> highlightedEntities = new List<Entity>();
553 
554  private bool UpdateHighlights(GraphicsDevice graphics, SpriteBatch spriteBatch, Matrix spriteBatchTransform, Camera cam)
555  {
556  if (GUI.DisableItemHighlights) { return false; }
557 
558  highlightedEntities.Clear();
559  if (Character.Controlled != null && (!Character.Controlled.IsKeyDown(InputType.Aim) || Character.Controlled.HeldItems.Any(it => it.GetComponent<Sprayer>() == null)))
560  {
561  if (Character.Controlled.FocusedItem != null)
562  {
563  highlightedEntities.Add(Character.Controlled.FocusedItem);
564  }
565  if (Character.Controlled.FocusedCharacter != null)
566  {
567  highlightedEntities.Add(Character.Controlled.FocusedCharacter);
568  }
569  foreach (MapEntity me in MapEntity.HighlightedEntities)
570  {
571  if (me is Item item && item != Character.Controlled.FocusedItem)
572  {
573  highlightedEntities.Add(item);
574  }
575  }
576  }
577  if (highlightedEntities.Count == 0) { return false; }
578 
579  //draw characters in light blue first
580  graphics.SetRenderTarget(HighlightMap);
581  SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
582  SolidColorEffect.Parameters["color"].SetValue(Color.LightBlue.ToVector4());
583  SolidColorEffect.CurrentTechnique.Passes[0].Apply();
584  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidColor"];
585  DeformableSprite.Effect.Parameters["solidColor"].SetValue(Color.LightBlue.ToVector4());
586  DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
587  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
588  foreach (Entity highlighted in highlightedEntities)
589  {
590  if (highlighted is Item item)
591  {
592  if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null))
593  {
594  //wait until next pass
595  }
596  else
597  {
598  item.Draw(spriteBatch, false, true);
599  }
600  }
601  else if (highlighted is Character character)
602  {
603  character.Draw(spriteBatch, cam);
604  }
605  }
606  spriteBatch.End();
607 
608  //draw items with iconstyles in the style's color
609  spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
610  foreach (Entity highlighted in highlightedEntities)
611  {
612  if (highlighted is Item item)
613  {
614  if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null))
615  {
616  SolidColorEffect.Parameters["color"].SetValue(item.IconStyle.Color.ToVector4());
617  SolidColorEffect.CurrentTechnique.Passes[0].Apply();
618  item.Draw(spriteBatch, false, true);
619  }
620  }
621  }
622  spriteBatch.End();
623 
624  //draw characters in black with a bit of blur, leaving the white edges visible
625  float phase = (float)(Math.Sin(Timing.TotalTime * 3.0f) + 1.0f) / 2.0f; //phase oscillates between 0 and 1
626  Vector4 overlayColor = Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase);
627  SolidColorEffect.Parameters["color"].SetValue(overlayColor);
628  SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColorBlur"];
629  SolidColorEffect.CurrentTechnique.Passes[0].Apply();
630  DeformableSprite.Effect.Parameters["solidColor"].SetValue(overlayColor);
631  DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
632  spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
633  foreach (Entity highlighted in highlightedEntities)
634  {
635  if (highlighted is Item item)
636  {
637  SolidColorEffect.Parameters["blurDistance"].SetValue(0.02f);
638  item.Draw(spriteBatch, false, true);
639  }
640  else if (highlighted is Character character)
641  {
642  SolidColorEffect.Parameters["blurDistance"].SetValue(0.05f);
643  character.Draw(spriteBatch, cam);
644  }
645  }
646  spriteBatch.End();
647 
648  //raster pattern on top of everything
649  spriteBatch.Begin(blendState: BlendState.NonPremultiplied, samplerState: SamplerState.LinearWrap);
650  spriteBatch.Draw(highlightRaster,
651  new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height),
652  new Rectangle(0, 0, (int)(HighlightMap.Width / currLightMapScale * 0.5f), (int)(HighlightMap.Height / currLightMapScale * 0.5f)),
653  Color.White * 0.5f);
654  spriteBatch.End();
655 
656  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
657 
658  return true;
659  }
660 
661  private readonly Dictionary<Hull, Rectangle> visibleHulls = new Dictionary<Hull, Rectangle>();
662  private Dictionary<Hull, Rectangle> GetVisibleHulls(Camera cam)
663  {
664  visibleHulls.Clear();
665  foreach (Hull hull in Hull.HullList)
666  {
667  if (hull.IsHidden) { continue; }
668  var drawRect =
669  hull.Submarine == null ?
670  hull.Rect :
671  new Rectangle((int)(hull.Submarine.DrawPosition.X + hull.Rect.X), (int)(hull.Submarine.DrawPosition.Y + hull.Rect.Y), hull.Rect.Width, hull.Rect.Height);
672 
673  if (drawRect.Right < cam.WorldView.X || drawRect.X > cam.WorldView.Right ||
674  drawRect.Y - drawRect.Height > cam.WorldView.Y || drawRect.Y < cam.WorldView.Y - cam.WorldView.Height)
675  {
676  continue;
677  }
678  visibleHulls.Add(hull, drawRect);
679  }
680  return visibleHulls;
681  }
682 
683  public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
684  {
685  if ((!LosEnabled || LosMode == LosMode.None) && ObstructVisionAmount <= 0.0f) { return; }
686  if (ViewTarget == null) return;
687 
688  graphics.SetRenderTarget(LosTexture);
689 
690  if (ObstructVisionAmount > 0.0f)
691  {
692  graphics.Clear(Color.Black);
693  Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
694  diff.Y = -diff.Y;
695  if (diff.LengthSquared() > 20.0f * 20.0f) { losOffset = diff; }
696  float rotation = MathUtils.VectorToAngle(losOffset);
697 
698  //the visible area stretches to the maximum when the cursor is this far from the character
699  const float MaxOffset = 256.0f;
700  //the magic numbers here are just based on experimentation
701  float MinHorizontalScale = MathHelper.Lerp(3.5f, 1.5f, ObstructVisionAmount);
702  float MaxHorizontalScale = MinHorizontalScale * 1.25f;
703  float VerticalScale = MathHelper.Lerp(4.0f, 1.25f, ObstructVisionAmount);
704 
705  //Starting point and scale-based modifier that moves the point of origin closer to the edge of the texture if the player moves their mouse further away, or vice versa.
706  float relativeOriginStartPosition = 0.1f; //Increasing this value moves the origin further behind the character
707  float originStartPosition = visionCircle.Width * relativeOriginStartPosition * MinHorizontalScale;
708  float relativeOriginLookAtPosModifier = -0.055f; //Increase this value increases how much the vision changes by moving the mouse
709  float originLookAtPosModifier = visionCircle.Width * relativeOriginLookAtPosModifier;
710 
711  Vector2 scale = new Vector2(
712  MathHelper.Clamp(losOffset.Length() / MaxOffset, MinHorizontalScale, MaxHorizontalScale), VerticalScale);
713 
714  spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameSettings.CurrentConfig.Graphics.LightMapScale, GameSettings.CurrentConfig.Graphics.LightMapScale, 1.0f)));
715  spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
716  new Vector2(originStartPosition + (scale.X * originLookAtPosModifier), visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
717  spriteBatch.End();
718  }
719  else
720  {
721  graphics.Clear(Color.White);
722  }
723 
724 
725  //--------------------------------------
726 
727  if (LosEnabled && LosMode != LosMode.None && ViewTarget != null)
728  {
729  Vector2 pos = ViewTarget.DrawPosition;
730  bool centeredOnHead = false;
731  if (ViewTarget is Character character &&
732  character.AnimController?.GetLimb(LimbType.Head) is Limb head &&
733  !head.IsSevered && !head.Removed)
734  {
735  pos = head.body.DrawPosition;
736  centeredOnHead = true;
737  }
738 
739  Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
740  Matrix shadowTransform = cam.ShaderTransform
741  * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
742 
743  var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width * 0.75f, ViewTarget.Submarine);
744 
745  //make sure the head isn't peeking through any LOS segments, and if it is,
746  //center the LOS on the character's collider instead
747  if (centeredOnHead)
748  {
749  foreach (var ch in convexHulls)
750  {
751  if (!ch.Enabled) { continue; }
752  Vector2 currentViewPos = pos;
753  Vector2 defaultViewPos = ViewTarget.DrawPosition;
754  if (ch.ParentEntity?.Submarine != null)
755  {
756  defaultViewPos -= ch.ParentEntity.Submarine.DrawPosition;
757  currentViewPos -= ch.ParentEntity.Submarine.DrawPosition;
758  }
759  //check if a line from the character's collider to the head intersects with the los segment (= head poking through it)
760  if (ch.LosIntersects(defaultViewPos, currentViewPos))
761  {
762  pos = ViewTarget.DrawPosition;
763  }
764  }
765  }
766 
767  if (convexHulls != null)
768  {
769  List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
770  List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
771  foreach (ConvexHull convexHull in convexHulls)
772  {
773  if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
774 
775  Vector2 relativeViewPos = pos;
776  if (convexHull.ParentEntity?.Submarine != null)
777  {
778  relativeViewPos -= convexHull.ParentEntity.Submarine.DrawPosition;
779  }
780 
781  convexHull.CalculateLosVertices(relativeViewPos);
782 
783  for (int i = 0; i < convexHull.ShadowVertexCount; i++)
784  {
785  shadowVerts.Add(convexHull.ShadowVertices[i]);
786  }
787 
788  for (int i = 0; i < convexHull.PenumbraVertexCount; i++)
789  {
790  penumbraVerts.Add(convexHull.PenumbraVertices[i]);
791  }
792  }
793 
794  if (shadowVerts.Count > 0)
795  {
796  ConvexHull.shadowEffect.World = shadowTransform;
797  ConvexHull.shadowEffect.CurrentTechnique.Passes[0].Apply();
798  graphics.DrawUserPrimitives(PrimitiveType.TriangleList, shadowVerts.ToArray(), 0, shadowVerts.Count / 3, VertexPositionColor.VertexDeclaration);
799 
800  if (penumbraVerts.Count > 0)
801  {
802  ConvexHull.penumbraEffect.World = shadowTransform;
803  ConvexHull.penumbraEffect.CurrentTechnique.Passes[0].Apply();
804  graphics.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVerts.ToArray(), 0, penumbraVerts.Count / 3, VertexPositionTexture.VertexDeclaration);
805  }
806  }
807  }
808  }
809  graphics.SetRenderTarget(null);
810  }
811 
812  public void DebugDrawLos(SpriteBatch spriteBatch, Camera cam)
813  {
814  Vector2 pos = ViewTarget?.Position ?? cam.Position;
815  spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform);
816  var convexHulls = ConvexHull.GetHullsInRange(pos, cam.WorldView.Width * 0.75f, ViewTarget?.Submarine);
817  Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
818  foreach (ConvexHull convexHull in convexHulls)
819  {
820  if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
821  if (convexHull.ParentEntity is Structure { CastShadow: false }) { continue; }
822  convexHull.DebugDraw(spriteBatch);
823  }
824  spriteBatch.End();
825  }
826 
827  public void ClearLights()
828  {
829  activeLights.Clear();
830  activeLightsWithLightVolume.Clear();
831  lights.Clear();
832  }
833  }
834 
835 
837  {
838  static CustomBlendStates()
839  {
840  Multiplicative = new BlendState
841  {
842  ColorSourceBlend = Blend.DestinationColor,
843  ColorDestinationBlend = Blend.SourceColor,
844  ColorBlendFunction = BlendFunction.Add
845  };
846  }
847  public static BlendState Multiplicative { get; private set; }
848  }
849 
850 }
Matrix Transform
Definition: Camera.cs:136
Vector2 Position
Definition: Camera.cs:398
Rectangle WorldView
Definition: Camera.cs:123
Matrix ShaderTransform
Definition: Camera.cs:141
Submarine Submarine
Definition: Entity.cs:53
static int GraphicsWidth
Definition: GameMain.cs:162
static int GraphicsHeight
Definition: GameMain.cs:168
Action ResolutionChanged
NOTE: Use very carefully. You need to ensure that you ALWAYS unsubscribe from this when you no longer...
Definition: GameMain.cs:133
static GameMain Instance
Definition: GameMain.cs:144
VertexPositionTexture[] PenumbraVertices
Definition: ConvexHull.cs:97
bool Intersects(Rectangle rect)
Definition: ConvexHull.cs:444
static List< ConvexHull > GetHullsInRange(Vector2 position, float range, Submarine ParentSub)
Definition: ConvexHull.cs:701
static BasicEffect shadowEffect
Definition: ConvexHull.cs:84
void DebugDraw(SpriteBatch spriteBatch)
Definition: ConvexHull.cs:661
static BasicEffect penumbraEffect
Definition: ConvexHull.cs:85
VertexPositionColor[] ShadowVertices
Definition: ConvexHull.cs:96
void CalculateLosVertices(Vector2 lightSourcePos)
Definition: ConvexHull.cs:507
void DebugDrawLos(SpriteBatch spriteBatch, Camera cam)
LightManager(GraphicsDevice graphics)
Definition: LightManager.cs:90
void AddRayCastTask(LightSource lightSource, Vector2 drawPos, float rotation)
void Update(float deltaTime)
void RemoveLight(LightSource light)
void DebugDrawVertices(SpriteBatch spriteBatch)
IEnumerable< LightSource > Lights
Definition: LightManager.cs:86
void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor=null)
void AddLight(LightSource light)
void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
void RayCastTask(Vector2 drawPos, float rotation)
Definition: LightSource.cs:745
void DebugDrawVertices(SpriteBatch spriteBatch)
HashSet< Submarine > HullsUpToDate
Definition: LightSource.cs:234
static Texture2D LightTexture
Definition: LightSource.cs:359
LightSourceParams LightSourceParams
Definition: LightSource.cs:291
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.