3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
6 using System.Collections.Generic;
14 private string debugName =
"Particle (uninitialized)";
21 private Vector2 position;
22 private Vector2 prevPosition;
24 private Vector2 velocity;
26 private float rotation;
27 private float prevRotation;
29 private float angularVelocity;
31 private float collisionIgnoreTimer = 0;
34 private Vector2 sizeChange;
37 private bool changeColor;
38 private bool UseMiddleColor;
40 private int spriteIndex;
42 private float totalLifeTime;
43 private float lifeTime;
45 private float startDelay;
47 private Vector2 velocityChange;
48 private Vector2 velocityChangeWater;
50 private Vector2 drawPosition;
51 private float drawRotation;
53 private Vector2 colliderRadius;
55 private Hull currentHull;
57 private List<Gap> hullGaps;
59 private bool hasSubEmitters;
60 private readonly List<ParticleEmitter> subEmitters =
new List<ParticleEmitter>();
62 private float animState;
63 private int animFrame;
65 private float collisionUpdateTimer;
67 private bool changesSize;
89 get {
return startDelay; }
90 set { startDelay = Math.Max(value, 0.0f); }
101 get {
return currentHull; }
106 get {
return prefab; }
115 this.prefab = prefab;
117 debugName = $
"Particle ({prefab.Name})";
120 debugName = prefab.
Name;
122 spriteIndex = Rand.Int(prefab.
Sprites.Count);
131 if (tracerPoints !=
null)
133 size =
new Vector2(Vector2.Distance(tracerPoints.Item1, tracerPoints.Item2), size.Y);
134 position = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
137 RefreshColliderSize();
140 changesSize = !sizeChange.NearlyEquals(Vector2.Zero);
142 this.position = position;
143 prevPosition = position;
145 drawPosition = position;
147 velocity = MathUtils.IsValid(speed) ? speed : Vector2.Zero;
155 prevRotation = rotation;
162 totalLifeTime = prefab.
LifeTime * lifeTimeMultiplier;
163 lifeTime = prefab.
LifeTime * lifeTimeMultiplier;
168 lifeTime = totalLifeTime * lifeTimeMultiplier;
189 hasSubEmitters =
false;
193 hasSubEmitters =
true;
198 hullGaps = currentHull ==
null ?
new List<Gap>() : currentHull.
ConnectedGaps;
203 this.rotation = MathUtils.VectorToAngle(
new Vector2(velocity.X, -velocity.Y));
205 prevRotation = rotation;
210 this.collisionIgnoreTimer = collisionIgnoreTimer;
221 if (startDelay > 0.0f)
223 startDelay -= deltaTime;
227 prevPosition = position;
228 prevRotation = rotation;
231 position.X += velocity.X * deltaTime;
232 position.Y += velocity.Y * deltaTime;
236 if (velocityChange != Vector2.Zero || angularVelocity != 0.0f)
238 Vector2 relativeVel = velocity;
243 rotation = MathUtils.VectorToAngle(
new Vector2(relativeVel.X, -relativeVel.Y));
248 rotation += angularVelocity * deltaTime;
265 if (prefab.
Drag > 0.0f)
267 ApplyDrag(prefab.
Drag, deltaTime);
273 size.X += sizeChange.X * deltaTime;
274 size.Y += sizeChange.Y * deltaTime;
275 RefreshColliderSize();
280 if (lifeTime > totalLifeTime * 0.5f)
299 animState += deltaTime;
303 animFrame = (int)(Math.Floor(animState / prefab.
AnimDuration * frameCount) % frameCount);
307 animFrame = (int)Math.Min(Math.Floor(animState / prefab.
AnimDuration * frameCount), frameCount - 1);
311 lifeTime -= deltaTime;
312 if (lifeTime <= 0.0f || color.A <= 0 || size.X <= 0.0f || size.Y <= 0.0f) {
return UpdateResult.Delete; }
318 emitter.
Emit(deltaTime, position, currentHull, particleRotation: rotation,
323 if (collisionIgnoreTimer > 0f)
325 collisionIgnoreTimer -= deltaTime;
326 if (collisionIgnoreTimer <= 0f) { currentHull ??=
Hull.
FindHull(position); }
333 return CollisionUpdate();
337 collisionUpdateTimer -= deltaTime;
338 if (collisionUpdateTimer <= 0.0f)
341 collisionUpdateTimer = 0.5f - Math.Min((Math.Abs(velocity.X) + Math.Abs(velocity.Y)) * 0.01f, 0.45f);
342 return CollisionUpdate();
351 if (currentHull ==
null)
354 if (collidedHull !=
null)
357 OnWallCollisionOutside(collidedHull);
363 Vector2 collisionNormal = Vector2.Zero;
364 if (velocity.Y < 0.0f && position.Y - colliderRadius.Y < hullRect.Y - hullRect.Height)
366 collisionNormal =
new Vector2(0.0f, 1.0f);
368 else if (velocity.Y > 0.0f && position.Y + colliderRadius.Y > hullRect.Y)
370 collisionNormal =
new Vector2(0.0f, -1.0f);
373 if (collisionNormal != Vector2.Zero)
375 bool gapFound =
false;
376 foreach (Gap gap
in hullGaps)
378 if (gap.Open <= 0.9f || gap.IsHorizontal) {
continue; }
380 if (gap.WorldRect.X > position.X || gap.WorldRect.Right < position.X) {
continue; }
382 int gapDir = Math.Sign(gap.WorldRect.Y - hullCenterY);
383 if (Math.Sign(velocity.Y) != gapDir || Math.Sign(position.Y - hullCenterY) != gapDir) {
continue; }
394 handleCollision(gapFound, collisionNormal);
397 collisionNormal = Vector2.Zero;
398 if (velocity.X < 0.0f && position.X - colliderRadius.X < hullRect.X)
400 collisionNormal =
new Vector2(1.0f, 0.0f);
402 else if (velocity.X > 0.0f && position.X + colliderRadius.X > hullRect.Right)
404 collisionNormal =
new Vector2(-1.0f, 0.0f);
407 if (collisionNormal != Vector2.Zero)
409 bool gapFound =
false;
410 foreach (Gap gap
in hullGaps)
412 if (gap.Open <= 0.9f || !gap.IsHorizontal) {
continue; }
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; }
426 handleCollision(gapFound, collisionNormal);
429 void handleCollision(
bool gapFound, Vector2 collisionNormal)
433 OnWallCollisionInside(currentHull, collisionNormal);
437 Hull newHull = Hull.FindHull(position, currentHull);
438 if (newHull != currentHull)
440 currentHull = newHull;
441 hullGaps = currentHull ==
null ?
new List<Gap>() : currentHull.ConnectedGaps;
451 private void RefreshColliderSize()
458 private void ApplyDrag(
float dragCoefficient,
float deltaTime)
460 Vector2 relativeVel = velocity;
461 if (currentHull?.Submarine !=
null)
463 relativeVel = velocity - ConvertUnits.ToDisplayUnits(currentHull.
Submarine.
Velocity);
466 float speed = relativeVel.Length();
468 relativeVel /= speed;
470 float drag = speed * speed * dragCoefficient * 0.01f * deltaTime;
473 relativeVel = Vector2.Zero;
478 relativeVel *= speed;
481 velocity = relativeVel;
482 if (currentHull?.Submarine !=
null)
489 private void OnWallCollisionInside(Hull prevHull, Vector2 collisionNormal)
491 if (prevHull ==
null) {
return; }
493 Rectangle prevHullRect = prevHull.WorldRect;
495 Vector2 subVel = prevHull?.Submarine !=
null ? ConvertUnits.ToDisplayUnits(prevHull.Submarine.Velocity) : Vector2.Zero;
498 if (Math.Abs(collisionNormal.X) > Math.Abs(collisionNormal.Y))
500 if (collisionNormal.X > 0.0f)
502 position.X = Math.Max(position.X, prevHullRect.X + colliderRadius.X);
506 position.X = Math.Min(position.X, prevHullRect.Right - colliderRadius.X);
508 velocity.X = Math.Sign(collisionNormal.X) * Math.Abs(velocity.X) * prefab.
Restitution;
509 velocity.Y *= (1.0f - prefab.
Friction);
513 if (collisionNormal.Y > 0.0f)
515 position.Y = Math.Max(position.Y, prevHullRect.Y - prevHullRect.Height + colliderRadius.Y);
519 position.Y = Math.Min(position.Y, prevHullRect.Y - colliderRadius.Y);
522 velocity.X *= (1.0f - prefab.
Friction);
523 velocity.Y = Math.Sign(collisionNormal.Y) * Math.Abs(velocity.Y) * prefab.
Restitution;
532 private void OnWallCollisionOutside(Hull collisionHull)
534 Rectangle hullRect = collisionHull.WorldRect;
536 Vector2 center =
new Vector2(hullRect.X + hullRect.Width / 2, hullRect.Y - hullRect.Height / 2);
538 if (position.Y < center.Y)
540 position.Y = hullRect.Y - hullRect.Height - colliderRadius.Y;
541 velocity.X *= (1.0f - prefab.
Friction);
544 else if (position.Y > center.Y)
546 position.Y = hullRect.Y + colliderRadius.Y;
547 velocity.X *= (1.0f - prefab.
Friction);
551 if (position.X < center.X)
553 position.X = hullRect.X - colliderRadius.X;
555 velocity.Y *= (1.0f - prefab.
Friction);
557 else if (position.X > center.X)
559 position.X = hullRect.X + hullRect.Width + colliderRadius.X;
561 velocity.Y *= (1.0f - prefab.
Friction);
571 drawPosition = Timing.Interpolate(prevPosition, position);
572 drawRotation = Timing.Interpolate(prevRotation, rotation);
575 public void Draw(SpriteBatch spriteBatch)
577 if (startDelay > 0.0f) {
return; }
579 Vector2 drawSize = size;
582 drawSize *= MathUtils.SmoothStep((totalLifeTime - lifeTime) / prefab.
GrowTime);
587 Vector2 drawPos =
new Vector2(drawPosition.X, -drawPosition.Y);
591 spriteBatch, animFrame,
593 currColor * (currColor.A / 255.0f),
594 prefab.
Sprites[spriteIndex].Origin, drawRotation,
595 drawSize, SpriteEffects.None, prefab.
Sprites[spriteIndex].Depth);
599 prefab.
Sprites[spriteIndex].Draw(spriteBatch,
601 currColor * (currColor.A / 255.0f),
602 prefab.
Sprites[spriteIndex].Origin, drawRotation,
603 drawSize, SpriteEffects.None, prefab.
Sprites[spriteIndex].Depth);
readonly List< Gap > ConnectedGaps
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
bool CopyParentParticleScale
float VelocityChangeMultiplier
override string ToString()
UpdateResult Update(float deltaTime)
bool HighQualityCollisionDetection
OnChangeHullHandler OnCollision
OnChangeHullHandler OnChangeHull
ParticleBlendState BlendState
ParticlePrefab.DrawTargetType DrawTarget
delegate void OnChangeHullHandler(Vector2 position, Hull currentHull)
void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess=null, ParticleDrawOrder drawOrder=ParticleDrawOrder.Default, float collisionIgnoreTimer=0f, float lifeTimeMultiplier=1f, Tuple< Vector2, Vector2 > tracerPoints=null)
void Draw(SpriteBatch spriteBatch)
ParticleDrawOrder DrawOrder
ParticleBlendState BlendState
float AngularVelocityMaxRad
DrawTargetType DrawTarget
Vector2 VelocityChangeWaterDisplay
readonly List< ParticleEmitterPrefab > SubEmitters
float StartRotationMaxRad
float StartRotationMinRad
bool InvariantCollisionSize
readonly List< Sprite > Sprites
Vector2 VelocityChangeDisplay
float AngularVelocityMinRad