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> activeShadowCastingLights = 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.IsPositionAboveLevel(light.WorldPosition)) { 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  activeShadowCastingLights.Clear();
319  foreach (var activeLight in activeLights)
320  {
321  if (!activeLight.CastShadows) { continue; }
322  if (activeLight.Range < 1.0f || activeLight.Color.A < 1 || activeLight.CurrentBrightness <= 0.0f) { continue; }
323  activeShadowCastingLights.Add(activeLight);
324  }
325 
326  //remove some lights with a light volume if there's too many of them
327  if (activeShadowCastingLights.Count > GameSettings.CurrentConfig.Graphics.VisibleLightLimit && Screen.Selected is { IsEditor: false })
328  {
329  for (int i = GameSettings.CurrentConfig.Graphics.VisibleLightLimit; i < activeShadowCastingLights.Count; i++)
330  {
331  activeLights.Remove(activeShadowCastingLights[i]);
332  }
333  }
334  activeLights.Sort((l1, l2) => l1.LastRecalculationTime.CompareTo(l2.LastRecalculationTime));
335 
336  //draw light sprites attached to characters
337  //render into a separate rendertarget using alpha blending (instead of on top of everything else with alpha blending)
338  //to prevent the lights from showing through other characters or other light sprites attached to the same character
339  //---------------------------------------------------------------------------------------------------
340  graphics.SetRenderTarget(LimbLightMap);
341  graphics.Clear(Color.Black);
342  graphics.BlendState = BlendState.NonPremultiplied;
343  spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
344  foreach (LightSource light in activeLights)
345  {
346  if (light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
347  //draw limb lights at this point, because they were skipped over previously to prevent them from being obstructed
348  if (light.ParentBody?.UserData is Limb limb && !limb.Hide) { light.DrawSprite(spriteBatch, cam); }
349  }
350  spriteBatch.End();
351 
352  //draw background lights
353  //---------------------------------------------------------------------------------------------------
354  graphics.SetRenderTarget(LightMap);
355  graphics.Clear(AmbientLight);
356  graphics.BlendState = BlendState.Additive;
357  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
358  Level.Loaded?.BackgroundCreatureManager?.DrawLights(spriteBatch, cam);
359  foreach (LightSource light in activeLights)
360  {
361  if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
362  light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
363  light.DrawSprite(spriteBatch, cam);
364  }
365  GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
366  spriteBatch.End();
367 
368  //draw a black rectangle on hulls to hide background lights behind subs
369  //---------------------------------------------------------------------------------------------------
370 
371  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform);
372  Dictionary<Hull, Rectangle> visibleHulls = GetVisibleHulls(cam);
373  foreach (KeyValuePair<Hull, Rectangle> hull in visibleHulls)
374  {
375  GUI.DrawRectangle(spriteBatch,
376  new Vector2(hull.Value.X, -hull.Value.Y),
377  new Vector2(hull.Value.Width, hull.Value.Height),
378  hull.Key.AmbientLight == Color.TransparentBlack ? Color.Black : hull.Key.AmbientLight.Multiply(hull.Key.AmbientLight.A / 255.0f), true);
379  }
380  spriteBatch.End();
381 
382  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
383  Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight);
384  glowColorHSV.Z = Math.Max(glowColorHSV.Z, 0.4f);
385  Color glowColor = ToolBoxCore.HSVToRGB(glowColorHSV.X, glowColorHSV.Y, glowColorHSV.Z);
386  Vector2 glowSpriteSize = new Vector2(gapGlowTexture.Width, gapGlowTexture.Height);
387  foreach (var gap in Gap.GapList)
388  {
389  if (gap.IsRoomToRoom || gap.Open <= 0.0f || gap.ConnectedWall == null) { continue; }
390 
391  float a = MathHelper.Lerp(0.5f, 1.0f,
392  PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.05f, gap.GlowEffectT));
393 
394  float scale = MathHelper.Lerp(0.5f, 2.0f,
395  PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.01f, gap.GlowEffectT));
396 
397  float rot = PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.001f, gap.GlowEffectT) * MathHelper.TwoPi;
398 
399  Vector2 spriteScale = new Vector2(gap.Rect.Width, gap.Rect.Height) / glowSpriteSize;
400  Vector2 drawPos = new Vector2(gap.DrawPosition.X, -gap.DrawPosition.Y);
401 
402  spriteBatch.Draw(gapGlowTexture,
403  drawPos,
404  null,
405  glowColor * a,
406  rot,
407  glowSpriteSize / 2,
408  scale: Math.Max(spriteScale.X, spriteScale.Y) * scale,
409  SpriteEffects.None,
410  layerDepth: 0);
411  }
412  spriteBatch.End();
413 
414  GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
415  GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
416  spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
417  Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
418  spriteBatch.End();
419 
420  graphics.BlendState = BlendState.Additive;
421 
422  //draw the focused item and character to highlight them,
423  //and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites)
424  //---------------------------------------------------------------------------------------------------
425  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
426  foreach (LightSource light in activeLights)
427  {
428  //don't draw limb lights at this point, they need to be drawn after lights have been obstructed by characters
429  if (light.IsBackground || light.ParentBody?.UserData is Limb || light.CurrentBrightness <= 0.0f) { continue; }
430  light.DrawSprite(spriteBatch, cam);
431  }
432  spriteBatch.End();
433 
434  if (highlightsVisible)
435  {
436  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
437  spriteBatch.Draw(HighlightMap, Vector2.Zero, Color.White);
438  spriteBatch.End();
439  }
440 
441  //draw characters to obstruct the highlighted items/characters and light sprites
442  //---------------------------------------------------------------------------------------------------
443  if (cam.Zoom > ObstructLightsBehindCharactersZoomThreshold)
444  {
445  SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"];
446  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
447  DrawCharacters(spriteBatch, cam, drawDeformSprites: false);
448  spriteBatch.End();
449 
450  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"];
451  DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
452  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
453  DrawCharacters(spriteBatch, cam, drawDeformSprites: true);
454  spriteBatch.End();
455  }
456 
457  static void DrawCharacters(SpriteBatch spriteBatch, Camera cam, bool drawDeformSprites)
458  {
459  foreach (Character character in Character.CharacterList)
460  {
461  if (character.CurrentHull == null || !character.Enabled || !character.IsVisible || character.InvisibleTimer > 0.0f) { continue; }
462  if (Character.Controlled?.FocusedCharacter == character) { continue; }
463  Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
464  Color.Black :
465  character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
466  foreach (Limb limb in character.AnimController.Limbs)
467  {
468  if (drawDeformSprites == (limb.DeformSprite == null)) { continue; }
469  limb.Draw(spriteBatch, cam, lightColor);
470  }
471  foreach (var heldItem in character.HeldItems)
472  {
473  heldItem.Draw(spriteBatch, editing: false, overrideColor: Color.Black);
474  }
475  }
476  }
477 
478  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
479  graphics.BlendState = BlendState.Additive;
480 
481  //draw the actual light volumes, additive particles, hull ambient lights and the halo around the player
482  //---------------------------------------------------------------------------------------------------
483  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
484 
485  spriteBatch.Draw(LimbLightMap, new Rectangle(cam.WorldView.X, -cam.WorldView.Y, cam.WorldView.Width, cam.WorldView.Height), Color.White);
486 
487  foreach (ElectricalDischarger discharger in ElectricalDischarger.List)
488  {
489  discharger.DrawElectricity(spriteBatch);
490  }
491 
492  foreach (LightSource light in activeLights)
493  {
494  if (light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
495  light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
496  }
497 
499  {
500  foreach (MapEntity e in (Submarine.VisibleEntities ?? MapEntity.MapEntityList))
501  {
502  if (e is Item item && !item.IsHidden && item.GetComponent<Wire>() is Wire wire)
503  {
504  wire.DebugDraw(spriteBatch, alpha: 0.4f);
505  }
506  }
507  }
508 
509  lightEffect.World = transform;
510 
511  GameMain.ParticleManager.Draw(spriteBatch, false, null, Particles.ParticleBlendState.Additive);
512 
513  if (Character.Controlled != null)
514  {
515  DrawHalo(Character.Controlled);
516  }
517  else
518  {
519  foreach (Character character in Character.CharacterList)
520  {
521  if (character.Submarine == null || character.IsDead || !character.IsHuman) { continue; }
522  DrawHalo(character);
523  }
524  }
525 
526  void DrawHalo(Character character)
527  {
528  if (character == null || character.Removed) { return; }
529  Vector2 haloDrawPos = character.DrawPosition;
530  haloDrawPos.Y = -haloDrawPos.Y;
531 
532  //ambient light decreases the brightness of the halo (no need for a bright halo if the ambient light is bright enough)
533  float ambientBrightness = (AmbientLight.R + AmbientLight.B + AmbientLight.G) / 255.0f / 3.0f;
534  Color haloColor = Color.White.Multiply(0.3f - ambientBrightness);
535  if (haloColor.A > 0)
536  {
537  float scale = 512.0f / LightSource.LightTexture.Width;
538  spriteBatch.Draw(
539  LightSource.LightTexture, haloDrawPos, null, haloColor, 0.0f,
540  new Vector2(LightSource.LightTexture.Width, LightSource.LightTexture.Height) / 2, scale, SpriteEffects.None, 0.0f);
541  }
542  }
543 
544  spriteBatch.End();
545 
546  //draw the actual light volumes, additive particles, hull ambient lights and the halo around the player
547  //---------------------------------------------------------------------------------------------------
548 
549  graphics.SetRenderTarget(null);
550  graphics.BlendState = BlendState.NonPremultiplied;
551  }
552 
553  private readonly List<Entity> highlightedEntities = new List<Entity>();
554 
555  private bool UpdateHighlights(GraphicsDevice graphics, SpriteBatch spriteBatch, Matrix spriteBatchTransform, Camera cam)
556  {
557  if (GUI.DisableItemHighlights) { return false; }
558 
559  highlightedEntities.Clear();
560  if (Character.Controlled != null && (!Character.Controlled.IsKeyDown(InputType.Aim) || Character.Controlled.HeldItems.Any(it => it.GetComponent<Sprayer>() == null)))
561  {
562  if (Character.Controlled.FocusedItem != null)
563  {
564  highlightedEntities.Add(Character.Controlled.FocusedItem);
565  }
566  if (Character.Controlled.FocusedCharacter != null)
567  {
568  highlightedEntities.Add(Character.Controlled.FocusedCharacter);
569  }
570  foreach (MapEntity me in MapEntity.HighlightedEntities)
571  {
572  if (me is Item item && item != Character.Controlled.FocusedItem)
573  {
574  highlightedEntities.Add(item);
575  }
576  }
577  }
578  if (highlightedEntities.Count == 0) { return false; }
579 
580  //draw characters in light blue first
581  graphics.SetRenderTarget(HighlightMap);
582  SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
583  SolidColorEffect.Parameters["color"].SetValue(Color.LightBlue.ToVector4());
584  SolidColorEffect.CurrentTechnique.Passes[0].Apply();
585  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidColor"];
586  DeformableSprite.Effect.Parameters["solidColor"].SetValue(Color.LightBlue.ToVector4());
587  DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
588  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
589  foreach (Entity highlighted in highlightedEntities)
590  {
591  if (highlighted is Item item)
592  {
593  if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null))
594  {
595  //wait until next pass
596  }
597  else
598  {
599  item.Draw(spriteBatch, false, true);
600  }
601  }
602  else if (highlighted is Character character)
603  {
604  character.Draw(spriteBatch, cam);
605  }
606  }
607  spriteBatch.End();
608 
609  //draw items with iconstyles in the style's color
610  spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
611  foreach (Entity highlighted in highlightedEntities)
612  {
613  if (highlighted is Item item)
614  {
615  if (item.IconStyle != null && (item != Character.Controlled.FocusedItem || Character.Controlled.FocusedItem == null))
616  {
617  SolidColorEffect.Parameters["color"].SetValue(item.IconStyle.Color.ToVector4());
618  SolidColorEffect.CurrentTechnique.Passes[0].Apply();
619  item.Draw(spriteBatch, false, true);
620  }
621  }
622  }
623  spriteBatch.End();
624 
625  //draw characters in black with a bit of blur, leaving the white edges visible
626  float phase = (float)(Math.Sin(Timing.TotalTime * 3.0f) + 1.0f) / 2.0f; //phase oscillates between 0 and 1
627  Vector4 overlayColor = Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase);
628  SolidColorEffect.Parameters["color"].SetValue(overlayColor);
629  SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColorBlur"];
630  SolidColorEffect.CurrentTechnique.Passes[0].Apply();
631  DeformableSprite.Effect.Parameters["solidColor"].SetValue(overlayColor);
632  DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
633  spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
634  foreach (Entity highlighted in highlightedEntities)
635  {
636  if (highlighted is Item item)
637  {
638  SolidColorEffect.Parameters["blurDistance"].SetValue(0.02f);
639  item.Draw(spriteBatch, false, true);
640  }
641  else if (highlighted is Character character)
642  {
643  SolidColorEffect.Parameters["blurDistance"].SetValue(0.05f);
644  character.Draw(spriteBatch, cam);
645  }
646  }
647  spriteBatch.End();
648 
649  //raster pattern on top of everything
650  spriteBatch.Begin(blendState: BlendState.NonPremultiplied, samplerState: SamplerState.LinearWrap);
651  spriteBatch.Draw(highlightRaster,
652  new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height),
653  new Rectangle(0, 0, (int)(HighlightMap.Width / currLightMapScale * 0.5f), (int)(HighlightMap.Height / currLightMapScale * 0.5f)),
654  Color.White * 0.5f);
655  spriteBatch.End();
656 
657  DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
658 
659  return true;
660  }
661 
662  private readonly Dictionary<Hull, Rectangle> visibleHulls = new Dictionary<Hull, Rectangle>();
663  private Dictionary<Hull, Rectangle> GetVisibleHulls(Camera cam)
664  {
665  visibleHulls.Clear();
666  foreach (Hull hull in Hull.HullList)
667  {
668  if (hull.IsHidden) { continue; }
669  var drawRect =
670  hull.Submarine == null ?
671  hull.Rect :
672  new Rectangle((int)(hull.Submarine.DrawPosition.X + hull.Rect.X), (int)(hull.Submarine.DrawPosition.Y + hull.Rect.Y), hull.Rect.Width, hull.Rect.Height);
673 
674  if (drawRect.Right < cam.WorldView.X || drawRect.X > cam.WorldView.Right ||
675  drawRect.Y - drawRect.Height > cam.WorldView.Y || drawRect.Y < cam.WorldView.Y - cam.WorldView.Height)
676  {
677  continue;
678  }
679  visibleHulls.Add(hull, drawRect);
680  }
681  return visibleHulls;
682  }
683 
684  public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
685  {
686  if ((!LosEnabled || LosMode == LosMode.None) && ObstructVisionAmount <= 0.0f) { return; }
687  if (ViewTarget == null) return;
688 
689  graphics.SetRenderTarget(LosTexture);
690 
691  if (ObstructVisionAmount > 0.0f)
692  {
693  graphics.Clear(Color.Black);
694  Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
695  diff.Y = -diff.Y;
696  if (diff.LengthSquared() > 20.0f * 20.0f) { losOffset = diff; }
697  float rotation = MathUtils.VectorToAngle(losOffset);
698 
699  //the visible area stretches to the maximum when the cursor is this far from the character
700  const float MaxOffset = 256.0f;
701  //the magic numbers here are just based on experimentation
702  float MinHorizontalScale = MathHelper.Lerp(3.5f, 1.5f, ObstructVisionAmount);
703  float MaxHorizontalScale = MinHorizontalScale * 1.25f;
704  float VerticalScale = MathHelper.Lerp(4.0f, 1.25f, ObstructVisionAmount);
705 
706  //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.
707  float relativeOriginStartPosition = 0.1f; //Increasing this value moves the origin further behind the character
708  float originStartPosition = visionCircle.Width * relativeOriginStartPosition * MinHorizontalScale;
709  float relativeOriginLookAtPosModifier = -0.055f; //Increase this value increases how much the vision changes by moving the mouse
710  float originLookAtPosModifier = visionCircle.Width * relativeOriginLookAtPosModifier;
711 
712  Vector2 scale = new Vector2(
713  MathHelper.Clamp(losOffset.Length() / MaxOffset, MinHorizontalScale, MaxHorizontalScale), VerticalScale);
714 
715  spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameSettings.CurrentConfig.Graphics.LightMapScale, GameSettings.CurrentConfig.Graphics.LightMapScale, 1.0f)));
716  spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
717  new Vector2(originStartPosition + (scale.X * originLookAtPosModifier), visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
718  spriteBatch.End();
719  }
720  else
721  {
722  graphics.Clear(Color.White);
723  }
724 
725 
726  //--------------------------------------
727 
728  if (LosEnabled && LosMode != LosMode.None && ViewTarget != null)
729  {
730  Vector2 pos = ViewTarget.DrawPosition;
731  bool centeredOnHead = false;
732  if (ViewTarget is Character character &&
733  character.AnimController?.GetLimb(LimbType.Head) is Limb head &&
734  !head.IsSevered && !head.Removed)
735  {
736  pos = head.body.DrawPosition;
737  centeredOnHead = true;
738  }
739 
740  Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
741  Matrix shadowTransform = cam.ShaderTransform
742  * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
743 
744  var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width * 0.75f, ViewTarget.Submarine);
745 
746  //make sure the head isn't peeking through any LOS segments, and if it is,
747  //center the LOS on the character's collider instead
748  if (centeredOnHead)
749  {
750  foreach (var ch in convexHulls)
751  {
752  if (!ch.Enabled) { continue; }
753  Vector2 currentViewPos = pos;
754  Vector2 defaultViewPos = ViewTarget.DrawPosition;
755  if (ch.ParentEntity?.Submarine != null)
756  {
757  defaultViewPos -= ch.ParentEntity.Submarine.DrawPosition;
758  currentViewPos -= ch.ParentEntity.Submarine.DrawPosition;
759  }
760  //check if a line from the character's collider to the head intersects with the los segment (= head poking through it)
761  if (ch.LosIntersects(defaultViewPos, currentViewPos))
762  {
763  pos = ViewTarget.DrawPosition;
764  }
765  }
766  }
767 
768  if (convexHulls != null)
769  {
770  List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
771  List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
772  foreach (ConvexHull convexHull in convexHulls)
773  {
774  if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
775 
776  Vector2 relativeViewPos = pos;
777  if (convexHull.ParentEntity?.Submarine != null)
778  {
779  relativeViewPos -= convexHull.ParentEntity.Submarine.DrawPosition;
780  }
781 
782  convexHull.CalculateLosVertices(relativeViewPos);
783 
784  for (int i = 0; i < convexHull.ShadowVertexCount; i++)
785  {
786  shadowVerts.Add(convexHull.ShadowVertices[i]);
787  }
788 
789  for (int i = 0; i < convexHull.PenumbraVertexCount; i++)
790  {
791  penumbraVerts.Add(convexHull.PenumbraVertices[i]);
792  }
793  }
794 
795  if (shadowVerts.Count > 0)
796  {
797  ConvexHull.shadowEffect.World = shadowTransform;
798  ConvexHull.shadowEffect.CurrentTechnique.Passes[0].Apply();
799  graphics.DrawUserPrimitives(PrimitiveType.TriangleList, shadowVerts.ToArray(), 0, shadowVerts.Count / 3, VertexPositionColor.VertexDeclaration);
800 
801  if (penumbraVerts.Count > 0)
802  {
803  ConvexHull.penumbraEffect.World = shadowTransform;
804  ConvexHull.penumbraEffect.CurrentTechnique.Passes[0].Apply();
805  graphics.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVerts.ToArray(), 0, penumbraVerts.Count / 3, VertexPositionTexture.VertexDeclaration);
806  }
807  }
808  }
809  }
810  graphics.SetRenderTarget(null);
811  }
812 
813  public void DebugDrawLos(SpriteBatch spriteBatch, Camera cam)
814  {
815  Vector2 pos = ViewTarget?.Position ?? cam.Position;
816  spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform);
817  var convexHulls = ConvexHull.GetHullsInRange(pos, cam.WorldView.Width * 0.75f, ViewTarget?.Submarine);
818  Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
819  foreach (ConvexHull convexHull in convexHulls)
820  {
821  if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
822  if (convexHull.ParentEntity is Structure { CastShadow: false }) { continue; }
823  convexHull.DebugDraw(spriteBatch);
824  }
825  spriteBatch.End();
826  }
827 
828  public void ClearLights()
829  {
830  activeLights.Clear();
831  activeShadowCastingLights.Clear();
832  lights.Clear();
833  }
834  }
835 
836 
838  {
839  static CustomBlendStates()
840  {
841  Multiplicative = new BlendState
842  {
843  ColorSourceBlend = Blend.DestinationColor,
844  ColorDestinationBlend = Blend.SourceColor,
845  ColorBlendFunction = BlendFunction.Add
846  };
847  }
848  public static BlendState Multiplicative { get; private set; }
849  }
850 
851 }
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
static bool IsPositionAboveLevel(Vector2 worldPosition)
Is the position above the upper boundary of the level ("outside bounds", where nothing should be able...
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:756
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, bool excludeLimbsWithSecondaryType=false, bool useSecondaryType=false)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
@ Character
Characters only