Client LuaCsForBarotrauma
Particle.cs
2 using FarseerPhysics;
3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
5 using System;
6 using System.Collections.Generic;
7 
9 {
10  class Particle
11  {
12  private ParticlePrefab prefab;
13 
14  private string debugName = "Particle (uninitialized)";
15 
16  public delegate void OnChangeHullHandler(Vector2 position, Hull currentHull);
18 
20 
21  private Vector2 position;
22  private Vector2 prevPosition;
23 
24  private Vector2 velocity;
25 
26  private float rotation;
27  private float prevRotation;
28 
29  private float angularVelocity;
30 
31  private float collisionIgnoreTimer = 0;
32 
33  private Vector2 size;
34  private Vector2 sizeChange;
35 
36  private Color color;
37  private bool changeColor;
38  private bool UseMiddleColor;
39 
40  private int spriteIndex;
41 
42  private float totalLifeTime;
43  private float lifeTime;
44 
45  private float startDelay;
46 
47  private Vector2 velocityChange;
48  private Vector2 velocityChangeWater;
49 
50  private Vector2 drawPosition;
51  private float drawRotation;
52 
53  private Vector2 colliderRadius;
54 
55  private Hull currentHull;
56 
57  private List<Gap> hullGaps;
58 
59  private bool hasSubEmitters;
60  private readonly List<ParticleEmitter> subEmitters = new List<ParticleEmitter>();
61 
62  private float animState;
63  private int animFrame;
64 
65  private float collisionUpdateTimer;
66 
67  private bool changesSize;
68 
70 
71  public Vector4 ColorMultiplier;
72 
74 
75  public bool DrawOnTop { get; private set; }
76 
78  {
79  get { return prefab.DrawTarget; }
80  }
81 
83  {
84  get { return prefab.BlendState; }
85  }
86 
87  public float StartDelay
88  {
89  get { return startDelay; }
90  set { startDelay = Math.Max(value, 0.0f); }
91  }
92 
93  public Vector2 Size
94  {
95  get { return size; }
96  set { size = value; }
97  }
98 
100  {
101  get { return currentHull; }
102  }
103 
105  {
106  get { return prefab; }
107  }
108 
109  public override string ToString()
110  {
111  return debugName;
112  }
113  public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
114  {
115  this.prefab = prefab;
116 #if DEBUG
117  debugName = $"Particle ({prefab.Name})";
118 #else
119  //don't instantiate new string objects in release builds
120  debugName = prefab.Name;
121 #endif
122  spriteIndex = Rand.Int(prefab.Sprites.Count);
123 
124  animState = 0;
125  animFrame = 0;
126 
127  currentHull = prefab.CanEnterSubs ? Hull.FindHull(position, hullGuess) : null;
128 
129  size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
130 
131  if (tracerPoints != null)
132  {
133  size = new Vector2(Vector2.Distance(tracerPoints.Item1, tracerPoints.Item2), size.Y);
134  position = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
135  }
136 
137  RefreshColliderSize();
138 
139  sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
140  changesSize = !sizeChange.NearlyEquals(Vector2.Zero);
141 
142  this.position = position;
143  prevPosition = position;
144 
145  drawPosition = position;
146 
147  velocity = MathUtils.IsValid(speed) ? speed : Vector2.Zero;
148 
149  if (currentHull?.Submarine != null)
150  {
151  velocity += ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
152  }
153 
154  this.rotation = rotation + Rand.Range(prefab.StartRotationMinRad, prefab.StartRotationMaxRad);
155  prevRotation = rotation;
156 
157  angularVelocity = Rand.Range(prefab.AngularVelocityMinRad, prefab.AngularVelocityMaxRad);
158 
159 
160  if (prefab.LifeTimeMin <= 0.0f)
161  {
162  totalLifeTime = prefab.LifeTime * lifeTimeMultiplier;
163  lifeTime = prefab.LifeTime * lifeTimeMultiplier;
164  }
165  else
166  {
167  totalLifeTime = Rand.Range(prefab.LifeTimeMin, prefab.LifeTime) * lifeTimeMultiplier;
168  lifeTime = totalLifeTime * lifeTimeMultiplier;
169  }
170 
171  startDelay = Rand.Range(prefab.StartDelayMin, prefab.StartDelayMax);
172 
173  UseMiddleColor = prefab.UseMiddleColor;
174  color = prefab.StartColor;
175  changeColor = prefab.StartColor != prefab.EndColor;
176  ColorMultiplier = Vector4.One;
177 
178  velocityChange = prefab.VelocityChangeDisplay;
179  velocityChangeWater = prefab.VelocityChangeWaterDisplay;
180 
182 
184 
185  OnChangeHull = null;
186  OnCollision = null;
187 
188  subEmitters.Clear();
189  hasSubEmitters = false;
190  foreach (ParticleEmitterPrefab emitterPrefab in prefab.SubEmitters)
191  {
192  subEmitters.Add(new ParticleEmitter(emitterPrefab));
193  hasSubEmitters = true;
194  }
195 
196  if (prefab.UseCollision)
197  {
198  hullGaps = currentHull == null ? new List<Gap>() : currentHull.ConnectedGaps;
199  }
200 
201  if (prefab.RotateToDirection)
202  {
203  this.rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
204 
205  prevRotation = rotation;
206  }
207 
208  DrawOnTop = drawOnTop;
209 
210  this.collisionIgnoreTimer = collisionIgnoreTimer;
211  }
212 
213  public enum UpdateResult
214  {
215  Normal,
216  Delete
217  }
218 
219  public UpdateResult Update(float deltaTime)
220  {
221  if (startDelay > 0.0f)
222  {
223  startDelay -= deltaTime;
224  return UpdateResult.Normal;
225  }
226 
227  prevPosition = position;
228  prevRotation = rotation;
229 
230  //over 3 times faster than position += velocity * deltatime
231  position.X += velocity.X * deltaTime;
232  position.Y += velocity.Y * deltaTime;
233 
234  if (prefab.RotateToDirection)
235  {
236  if (velocityChange != Vector2.Zero || angularVelocity != 0.0f)
237  {
238  Vector2 relativeVel = velocity;
239  if (currentHull?.Submarine != null)
240  {
241  relativeVel -= ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
242  }
243  rotation = MathUtils.VectorToAngle(new Vector2(relativeVel.X, -relativeVel.Y));
244  }
245  }
246  else
247  {
248  rotation += angularVelocity * deltaTime;
249  }
250 
251  bool inWater = (currentHull == null || (currentHull.Submarine != null && position.Y - currentHull.Submarine.DrawPosition.Y < currentHull.Surface));
252  if (inWater)
253  {
254  velocity.X += velocityChangeWater.X * VelocityChangeMultiplier * deltaTime;
255  velocity.Y += velocityChangeWater.Y * VelocityChangeMultiplier * deltaTime;
256  if (prefab.WaterDrag > 0.0f)
257  {
258  ApplyDrag(prefab.WaterDrag, deltaTime);
259  }
260  }
261  else
262  {
263  velocity.X += velocityChange.X * VelocityChangeMultiplier * deltaTime;
264  velocity.Y += velocityChange.Y * VelocityChangeMultiplier * deltaTime;
265  if (prefab.Drag > 0.0f)
266  {
267  ApplyDrag(prefab.Drag, deltaTime);
268  }
269  }
270 
271  if (changesSize)
272  {
273  size.X += sizeChange.X * deltaTime;
274  size.Y += sizeChange.Y * deltaTime;
275  RefreshColliderSize();
276  }
277 
278  if (UseMiddleColor)
279  {
280  if (lifeTime > totalLifeTime * 0.5f)
281  {
282  color = Color.Lerp(prefab.MiddleColor, prefab.StartColor, (lifeTime / totalLifeTime - 0.5f) * 2.0f);
283  }
284  else
285  {
286  color = Color.Lerp(prefab.EndColor, prefab.MiddleColor, lifeTime / totalLifeTime * 2.0f);
287  }
288  }
289  else
290  {
291  if (changeColor)
292  {
293  color = Color.Lerp(prefab.EndColor, prefab.StartColor, lifeTime / totalLifeTime);
294  }
295  }
296 
297  if (prefab.Sprites[spriteIndex] is SpriteSheet)
298  {
299  animState += deltaTime;
300  int frameCount = ((SpriteSheet)prefab.Sprites[spriteIndex]).FrameCount;
301  if (prefab.LoopAnim)
302  {
303  animFrame = (int)(Math.Floor(animState / prefab.AnimDuration * frameCount) % frameCount);
304  }
305  else
306  {
307  animFrame = (int)Math.Min(Math.Floor(animState / prefab.AnimDuration * frameCount), frameCount - 1);
308  }
309  }
310 
311  lifeTime -= deltaTime;
312  if (lifeTime <= 0.0f || color.A <= 0 || size.X <= 0.0f || size.Y <= 0.0f) { return UpdateResult.Delete; }
313 
314  if (hasSubEmitters)
315  {
316  foreach (ParticleEmitter emitter in subEmitters)
317  {
318  emitter.Emit(deltaTime, position, currentHull, particleRotation: rotation,
319  sizeMultiplier: emitter.Prefab.Properties.CopyParentParticleScale ? Math.Max(size.X, size.Y) : 1.0f);
320  }
321  }
322 
323  if (collisionIgnoreTimer > 0f)
324  {
325  collisionIgnoreTimer -= deltaTime;
326  if (collisionIgnoreTimer <= 0f) { currentHull ??= Hull.FindHull(position); }
327  return UpdateResult.Normal;
328  }
329  if (!prefab.UseCollision) { return UpdateResult.Normal; }
330 
332  {
333  return CollisionUpdate();
334  }
335  else
336  {
337  collisionUpdateTimer -= deltaTime;
338  if (collisionUpdateTimer <= 0.0f)
339  {
340  //more frequent collision updates if the particle is moving fast
341  collisionUpdateTimer = 0.5f - Math.Min((Math.Abs(velocity.X) + Math.Abs(velocity.Y)) * 0.01f, 0.45f);
342  return CollisionUpdate();
343  }
344  }
345 
346  return UpdateResult.Normal;
347  }
348 
349  private UpdateResult CollisionUpdate()
350  {
351  if (currentHull == null)
352  {
353  Hull collidedHull = Hull.FindHull(position);
354  if (collidedHull != null)
355  {
356  if (prefab.DeleteOnCollision) { return UpdateResult.Delete; }
357  OnWallCollisionOutside(collidedHull);
358  }
359  }
360  else
361  {
362  Rectangle hullRect = currentHull.WorldRect;
363  Vector2 collisionNormal = Vector2.Zero;
364  if (velocity.Y < 0.0f && position.Y - colliderRadius.Y < hullRect.Y - hullRect.Height)
365  {
366  collisionNormal = new Vector2(0.0f, 1.0f);
367  }
368  else if (velocity.Y > 0.0f && position.Y + colliderRadius.Y > hullRect.Y)
369  {
370  collisionNormal = new Vector2(0.0f, -1.0f);
371  }
372 
373  if (collisionNormal != Vector2.Zero)
374  {
375  bool gapFound = false;
376  foreach (Gap gap in hullGaps)
377  {
378  if (gap.Open <= 0.9f || gap.IsHorizontal) { continue; }
379 
380  if (gap.WorldRect.X > position.X || gap.WorldRect.Right < position.X) { continue; }
381  float hullCenterY = currentHull.WorldRect.Y - currentHull.WorldRect.Height / 2;
382  int gapDir = Math.Sign(gap.WorldRect.Y - hullCenterY);
383  if (Math.Sign(velocity.Y) != gapDir || Math.Sign(position.Y - hullCenterY) != gapDir) { continue; }
384 
385  gapFound = true;
386  break;
387  }
388 
389  if (prefab.DeleteOnCollision && !gapFound)
390  {
391  OnCollision?.Invoke(position, currentHull);
392  return UpdateResult.Delete;
393  }
394  handleCollision(gapFound, collisionNormal);
395  }
396 
397  collisionNormal = Vector2.Zero;
398  if (velocity.X < 0.0f && position.X - colliderRadius.X < hullRect.X)
399  {
400  collisionNormal = new Vector2(1.0f, 0.0f);
401  }
402  else if (velocity.X > 0.0f && position.X + colliderRadius.X > hullRect.Right)
403  {
404  collisionNormal = new Vector2(-1.0f, 0.0f);
405  }
406 
407  if (collisionNormal != Vector2.Zero)
408  {
409  bool gapFound = false;
410  foreach (Gap gap in hullGaps)
411  {
412  if (gap.Open <= 0.9f || !gap.IsHorizontal) { continue; }
413 
414  if (gap.WorldRect.Y < position.Y || gap.WorldRect.Y - gap.WorldRect.Height > position.Y) { continue; }
415  int gapDir = Math.Sign(gap.WorldRect.Center.X - currentHull.WorldRect.Center.X);
416  if (Math.Sign(velocity.X) != gapDir || Math.Sign(position.X - currentHull.WorldRect.Center.X) != gapDir) { continue; }
417 
418  gapFound = true;
419  break;
420  }
421  if (prefab.DeleteOnCollision && !gapFound)
422  {
423  OnCollision?.Invoke(position, currentHull);
424  return UpdateResult.Delete;
425  }
426  handleCollision(gapFound, collisionNormal);
427  }
428 
429  void handleCollision(bool gapFound, Vector2 collisionNormal)
430  {
431  if (!gapFound)
432  {
433  OnWallCollisionInside(currentHull, collisionNormal);
434  }
435  else
436  {
437  Hull newHull = Hull.FindHull(position, currentHull);
438  if (newHull != currentHull)
439  {
440  currentHull = newHull;
441  hullGaps = currentHull == null ? new List<Gap>() : currentHull.ConnectedGaps;
442  OnChangeHull?.Invoke(position, currentHull);
443  }
444  }
445  }
446  }
447 
448  return UpdateResult.Normal;
449  }
450 
451  private void RefreshColliderSize()
452  {
453  if (!prefab.UseCollision) { return; }
454  colliderRadius = new Vector2(prefab.CollisionRadius);
455  if (!prefab.InvariantCollisionSize) { colliderRadius *= size; }
456  }
457 
458  private void ApplyDrag(float dragCoefficient, float deltaTime)
459  {
460  Vector2 relativeVel = velocity;
461  if (currentHull?.Submarine != null)
462  {
463  relativeVel = velocity - ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
464  }
465 
466  float speed = relativeVel.Length();
467 
468  relativeVel /= speed;
469 
470  float drag = speed * speed * dragCoefficient * 0.01f * deltaTime;
471  if (drag > speed)
472  {
473  relativeVel = Vector2.Zero;
474  }
475  else
476  {
477  speed -= drag;
478  relativeVel *= speed;
479  }
480 
481  velocity = relativeVel;
482  if (currentHull?.Submarine != null)
483  {
484  velocity += ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
485  }
486  }
487 
488 
489  private void OnWallCollisionInside(Hull prevHull, Vector2 collisionNormal)
490  {
491  if (prevHull == null) { return; }
492 
493  Rectangle prevHullRect = prevHull.WorldRect;
494 
495  Vector2 subVel = prevHull?.Submarine != null ? ConvertUnits.ToDisplayUnits(prevHull.Submarine.Velocity) : Vector2.Zero;
496  velocity -= subVel;
497 
498  if (Math.Abs(collisionNormal.X) > Math.Abs(collisionNormal.Y))
499  {
500  if (collisionNormal.X > 0.0f)
501  {
502  position.X = Math.Max(position.X, prevHullRect.X + colliderRadius.X);
503  }
504  else
505  {
506  position.X = Math.Min(position.X, prevHullRect.Right - colliderRadius.X);
507  }
508  velocity.X = Math.Sign(collisionNormal.X) * Math.Abs(velocity.X) * prefab.Restitution;
509  velocity.Y *= (1.0f - prefab.Friction);
510  }
511  else
512  {
513  if (collisionNormal.Y > 0.0f)
514  {
515  position.Y = Math.Max(position.Y, prevHullRect.Y - prevHullRect.Height + colliderRadius.Y);
516  }
517  else
518  {
519  position.Y = Math.Min(position.Y, prevHullRect.Y - colliderRadius.Y);
520 
521  }
522  velocity.X *= (1.0f - prefab.Friction);
523  velocity.Y = Math.Sign(collisionNormal.Y) * Math.Abs(velocity.Y) * prefab.Restitution;
524  }
525 
526  OnCollision?.Invoke(position, currentHull);
527 
528  velocity += subVel;
529  }
530 
531 
532  private void OnWallCollisionOutside(Hull collisionHull)
533  {
534  Rectangle hullRect = collisionHull.WorldRect;
535 
536  Vector2 center = new Vector2(hullRect.X + hullRect.Width / 2, hullRect.Y - hullRect.Height / 2);
537 
538  if (position.Y < center.Y)
539  {
540  position.Y = hullRect.Y - hullRect.Height - colliderRadius.Y;
541  velocity.X *= (1.0f - prefab.Friction);
542  velocity.Y = -velocity.Y * prefab.Restitution;
543  }
544  else if (position.Y > center.Y)
545  {
546  position.Y = hullRect.Y + colliderRadius.Y;
547  velocity.X *= (1.0f - prefab.Friction);
548  velocity.Y = -velocity.Y * prefab.Restitution;
549  }
550 
551  if (position.X < center.X)
552  {
553  position.X = hullRect.X - colliderRadius.X;
554  velocity.X = -velocity.X * prefab.Restitution;
555  velocity.Y *= (1.0f - prefab.Friction);
556  }
557  else if (position.X > center.X)
558  {
559  position.X = hullRect.X + hullRect.Width + colliderRadius.X;
560  velocity.X = -velocity.X * prefab.Restitution;
561  velocity.Y *= (1.0f - prefab.Friction);
562  }
563 
564  OnCollision?.Invoke(position, currentHull);
565 
566  velocity *= prefab.Restitution;
567  }
568 
569  public void UpdateDrawPos()
570  {
571  drawPosition = Timing.Interpolate(prevPosition, position);
572  drawRotation = Timing.Interpolate(prevRotation, rotation);
573  }
574 
575  public void Draw(SpriteBatch spriteBatch)
576  {
577  if (startDelay > 0.0f) { return; }
578 
579  Vector2 drawSize = size;
580  if (prefab.GrowTime > 0.0f && totalLifeTime - lifeTime < prefab.GrowTime)
581  {
582  drawSize *= MathUtils.SmoothStep((totalLifeTime - lifeTime) / prefab.GrowTime);
583  }
584 
585  Color currColor = new Color(color.ToVector4() * ColorMultiplier);
586 
587  Vector2 drawPos = new Vector2(drawPosition.X, -drawPosition.Y);
588  if (prefab.Sprites[spriteIndex] is SpriteSheet sheet)
589  {
590  sheet.Draw(
591  spriteBatch, animFrame,
592  drawPos,
593  currColor * (currColor.A / 255.0f),
594  prefab.Sprites[spriteIndex].Origin, drawRotation,
595  drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
596  }
597  else
598  {
599  prefab.Sprites[spriteIndex].Draw(spriteBatch,
600  drawPos,
601  currColor * (currColor.A / 255.0f),
602  prefab.Sprites[spriteIndex].Origin, drawRotation,
603  drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
604  }
605 
606  /*if (GameMain.DebugDraw && prefab.UseCollision)
607  {
608  GUI.DrawLine(spriteBatch,
609  drawPos - Vector2.UnitX * colliderRadius.X,
610  drawPos + Vector2.UnitX * colliderRadius.X,
611  Color.Gray);
612  GUI.DrawLine(spriteBatch,
613  drawPos - Vector2.UnitY * colliderRadius.Y,
614  drawPos + Vector2.UnitY * colliderRadius.Y,
615  Color.Gray);
616  }*/
617  }
618  }
619 }
Submarine Submarine
Definition: Entity.cs:53
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
readonly ParticleEmitterPrefab Prefab
void Emit(float deltaTime, Vector2 position, Hull? hullGuess=null, float angle=0.0f, float particleRotation=0.0f, float velocityMultiplier=1.0f, float sizeMultiplier=1.0f, float amountMultiplier=1.0f, Color? colorMultiplier=null, ParticlePrefab? overrideParticle=null, bool mirrorAngle=false, Tuple< Vector2, Vector2 >? tracerPoints=null)
readonly ParticleEmitterProperties Properties
override string ToString()
Definition: Particle.cs:109
UpdateResult Update(float deltaTime)
Definition: Particle.cs:219
OnChangeHullHandler OnCollision
Definition: Particle.cs:19
OnChangeHullHandler OnChangeHull
Definition: Particle.cs:17
ParticleBlendState BlendState
Definition: Particle.cs:83
ParticlePrefab.DrawTargetType DrawTarget
Definition: Particle.cs:78
delegate void OnChangeHullHandler(Vector2 position, Hull currentHull)
void Draw(SpriteBatch spriteBatch)
Definition: Particle.cs:575
void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess=null, bool drawOnTop=false, float collisionIgnoreTimer=0f, float lifeTimeMultiplier=1f, Tuple< Vector2, Vector2 > tracerPoints=null)
Definition: Particle.cs:113
readonly List< ParticleEmitterPrefab > SubEmitters
readonly List< Sprite > Sprites