Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Physics/PhysicsBody.cs
2 using FarseerPhysics;
3 using FarseerPhysics.Dynamics;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Xml.Linq;
8 using LimbParams = Barotrauma.RagdollParams.LimbParams;
9 using ColliderParams = Barotrauma.RagdollParams.ColliderParams;
10 
11 namespace Barotrauma
12 {
13  class PosInfo
14  {
15  public Vector2 Position
16  {
17  get;
18  private set;
19  }
20 
21  public float? Rotation
22  {
23  get;
24  private set;
25  }
26 
27  public Vector2 LinearVelocity
28  {
29  get;
30  private set;
31  }
32 
33  public float? AngularVelocity
34  {
35  get;
36  private set;
37  }
38 
39  public readonly float Timestamp;
40  public readonly UInt16 ID;
41 
42  public PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, float time)
43  : this(pos, rotation, linearVelocity, angularVelocity, 0, time)
44  {
45  }
46 
47  public PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID)
48  : this(pos, rotation, linearVelocity, angularVelocity, ID, 0.0f)
49  {
50  }
51 
52  protected PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID, float time)
53  {
54  Position = pos;
55  Rotation = rotation;
56  LinearVelocity = linearVelocity;
57  AngularVelocity = angularVelocity;
58 
59  this.ID = ID;
60  Timestamp = time;
61  }
62 
63  public void TransformOutToInside(Submarine submarine)
64  {
65  //transform outside coordinates to in-sub coordinates
66  Position -= ConvertUnits.ToSimUnits(submarine.Position);
67  }
68 
69  public void TransformInToOutside()
70  {
71  var sub = Submarine.FindContainingInLocalCoordinates(ConvertUnits.ToDisplayUnits(Position));
72  if (sub != null)
73  {
74  Position += ConvertUnits.ToSimUnits(sub.Position);
75  }
76  }
77 
78  public void Translate(Vector2 posAmount,float rotationAmount)
79  {
80  Position += posAmount; Rotation += rotationAmount;
81  }
82  }
83 
84  partial class PhysicsBody
85  {
86  public enum Shape
87  {
88  Circle, Rectangle, Capsule, HorizontalCapsule
89  };
90 
91  public const float MinDensity = 0.01f;
92  public const float DefaultAngularDamping = 5.0f;
93 
94  private static readonly List<PhysicsBody> list = new List<PhysicsBody>();
95  public static List<PhysicsBody> List
96  {
97  get { return list; }
98  }
99 
100  protected Vector2 prevPosition;
101  protected float prevRotation;
102 
103  protected Vector2? targetPosition;
104  protected float? targetRotation;
105 
106  private Vector2 drawPosition;
107  private float drawRotation;
108 
109  public bool Removed
110  {
111  get;
112  private set;
113  }
114 
115  public Vector2 LastSentPosition
116  {
117  get;
118  private set;
119  }
120 
121  private Shape bodyShape;
122  public float Height { get; private set; }
123  public float Width { get; private set; }
124  public float Radius { get; private set; }
125 
126  private readonly float density;
127 
128  //the direction the item is facing (for example, a gun has to be
129  //flipped horizontally if the Character holding it turns around)
130  float dir = 1.0f;
131 
132  private Vector2 drawOffset;
133  private float rotationOffset;
134 
135  private float lastProcessedNetworkState;
136 
137  public float? PositionSmoothingFactor;
138 
140  {
141  get { return bodyShape; }
142  }
143 
144  public Vector2? TargetPosition
145  {
146  get { return targetPosition; }
147  set
148  {
149  if (value == null)
150  {
151  targetPosition = null;
152  }
153  else
154  {
155  if (!IsValidValue(value.Value, "target position", -1e5f, 1e5f)) return;
156  targetPosition = new Vector2(
157  MathHelper.Clamp(((Vector2)value).X, -10000.0f, 10000.0f),
158  MathHelper.Clamp(((Vector2)value).Y, -10000.0f, 10000.0f));
159  }
160  }
161  }
162 
163  public float? TargetRotation
164  {
165  get { return targetRotation; }
166  set
167  {
168  if (value == null)
169  {
170  targetRotation = null;
171  }
172  else
173  {
174  if (!IsValidValue(value.Value, "target rotation")) return;
175  targetRotation = value;
176  }
177 
178  }
179  }
180 
181  public Vector2 DrawPosition
182  {
183  get { return Submarine == null ? drawPosition : drawPosition + Submarine.DrawPosition; }
184  }
185 
186  public float DrawRotation
187  {
188  get { return drawRotation; }
189  }
190 
192 
193  public float Dir
194  {
195  get { return dir; }
196  set { dir = value; }
197  }
198 
199  private bool isEnabled = true;
200  private bool isPhysEnabled = true;
201 
202  public bool Enabled
203  {
204  get { return isEnabled; }
205  set
206  {
207  isEnabled = value;
208  try
209  {
210  if (isEnabled) FarseerBody.Enabled = isPhysEnabled; else FarseerBody.Enabled = false;
211  }
212  catch (Exception e)
213  {
214  DebugConsole.ThrowError("Exception in PhysicsBody.Enabled = " + value + " (" + isPhysEnabled + ")", e);
215  if (UserData != null) DebugConsole.NewMessage("PhysicsBody UserData: " + UserData.GetType().ToString(), Color.Red);
216  if (GameMain.World.ContactManager == null) DebugConsole.NewMessage("ContactManager is null!", Color.Red);
217  else if (GameMain.World.ContactManager.BroadPhase == null) DebugConsole.NewMessage("Broadphase is null!", Color.Red);
218  if (FarseerBody.FixtureList == null) DebugConsole.NewMessage("FixtureList is null!", Color.Red);
219 
220  if (UserData is Entity entity)
221  {
222  DebugConsole.NewMessage("Entity \"" + entity.ToString() + "\" removed!", Color.Red);
223  }
224  }
225  }
226  }
227 
228  public bool PhysEnabled
229  {
230  get { return FarseerBody.Enabled; }
231  set
232  {
233  isPhysEnabled = value;
234  if (Enabled)
235  {
236  FarseerBody.Enabled = value;
237  }
238  }
239  }
240 
241  public Vector2 SimPosition
242  {
243  get { return FarseerBody.Position; }
244  }
245 
246  public Vector2 Position
247  {
248  get { return ConvertUnits.ToDisplayUnits(FarseerBody.Position); }
249  }
250 
255 
256  public Vector2 PrevPosition
257  {
258  get { return prevPosition; }
259  }
260 
261  public float Rotation
262  {
263  get { return FarseerBody.Rotation; }
264  }
265 
270 
271  public float TransformRotation(float rotation) => TransformRotation(rotation, dir);
272 
273  public static float TransformRotation(float rot, float dir) => dir < 0 ? rot - MathHelper.Pi : rot;
274 
275  public Vector2 LinearVelocity
276  {
277  get { return FarseerBody.LinearVelocity; }
278  set
279  {
280  if (!IsValidValue(value, "velocity", -1000.0f, 1000.0f)) return;
281  FarseerBody.LinearVelocity = value;
282  }
283  }
284 
285  public float AngularVelocity
286  {
287  get { return FarseerBody.AngularVelocity; }
288  set
289  {
290  if (!IsValidValue(value, "angular velocity", -1000f, 1000f)) return;
291  FarseerBody.AngularVelocity = value;
292  }
293  }
294 
295  public float Mass
296  {
297  get { return FarseerBody.Mass; }
298  }
299 
300  public float Density
301  {
302  get { return density; }
303  }
304 
305  public Body FarseerBody { get; private set; }
306 
307  public object UserData
308  {
309  get { return FarseerBody.UserData; }
310  set { FarseerBody.UserData = value; }
311  }
312 
313  public float Friction
314  {
315  set { FarseerBody.Friction = value; }
316  }
317 
319  {
320  get { return FarseerBody.BodyType; }
321  set { FarseerBody.BodyType = value; }
322  }
323 
324  private Category _collisionCategories;
325 
326  public Category CollisionCategories
327  {
328  set
329  {
330  _collisionCategories = value;
331  FarseerBody.CollisionCategories = value;
332  }
333  get
334  {
335  return _collisionCategories;
336  }
337  }
338 
339  private Category _collidesWith;
340  public Category CollidesWith
341  {
342  set
343  {
344  _collidesWith = value;
345  FarseerBody.CollidesWith = value;
346  }
347  get
348  {
349  return _collidesWith;
350  }
351  }
352 
357  {
358  get => _suppressSmoothRotationCalls;
359  set
360  {
361  _suppressSmoothRotationCalls = value;
362  smoothRotationSuppressionCounter = 0;
363  }
364  }
365 
366  private bool _suppressSmoothRotationCalls;
367  private int smoothRotationSuppressionCounter;
368 
369  public PhysicsBody(XElement element, float scale = 1.0f, bool findNewContacts = true) : this(element, Vector2.Zero, scale, findNewContacts: findNewContacts) { }
370  public PhysicsBody(ColliderParams cParams, bool findNewContacts = true) : this(cParams, Vector2.Zero, findNewContacts) { }
371  public PhysicsBody(LimbParams lParams, bool findNewContacts = true) : this(lParams, Vector2.Zero, findNewContacts) { }
372 
373  public PhysicsBody(float width, float height, float radius, float density, BodyType bodyType, Category collisionCategory, Category collidesWith, bool findNewContacts = true)
374  {
375  density = Math.Max(density, MinDensity);
376  CreateBody(width, height, radius, density, bodyType, collisionCategory, collidesWith, findNewContacts);
377  LastSentPosition = FarseerBody.Position;
378  list.Add(this);
379  }
380 
381  public PhysicsBody(Body farseerBody)
382  {
383  FarseerBody = farseerBody;
384  if (FarseerBody.UserData == null) { FarseerBody.UserData = this; }
385  LastSentPosition = FarseerBody.Position;
386  list.Add(this);
387  }
388 
389  public PhysicsBody(ColliderParams colliderParams, Vector2 position, bool findNewContacts = true)
390  {
391  float radius = ConvertUnits.ToSimUnits(colliderParams.Radius) * colliderParams.Ragdoll.LimbScale;
392  float height = ConvertUnits.ToSimUnits(colliderParams.Height) * colliderParams.Ragdoll.LimbScale;
393  float width = ConvertUnits.ToSimUnits(colliderParams.Width) * colliderParams.Ragdoll.LimbScale;
394  density = Physics.NeutralDensity;
395  CreateBody(width, height, radius, density, colliderParams.BodyType,
396  Physics.CollisionCharacter,
397  Physics.CollisionWall | Physics.CollisionLevel,
398  findNewContacts);
399  FarseerBody.AngularDamping = DefaultAngularDamping;
400  FarseerBody.FixedRotation = true;
401  FarseerBody.Friction = 0.05f;
402  FarseerBody.Restitution = 0.05f;
403  SetTransformIgnoreContacts(position, 0.0f);
404  LastSentPosition = position;
405  list.Add(this);
406  }
407 
408  public PhysicsBody(LimbParams limbParams, Vector2 position, bool findNewContacts = true)
409  {
410  float radius = ConvertUnits.ToSimUnits(limbParams.Radius) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
411  float height = ConvertUnits.ToSimUnits(limbParams.Height) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
412  float width = ConvertUnits.ToSimUnits(limbParams.Width) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
413  density = Math.Max(limbParams.Density, MinDensity);
414 
415  Category collisionCategory = Physics.CollisionCharacter;
416  Category collidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
417  if (limbParams.IgnoreCollisions)
418  {
419  collisionCategory = Category.None;
420  collidesWith = Category.None;
421  }
422  CreateBody(width, height, radius, density, BodyType.Dynamic,
423  collisionCategory: collisionCategory,
424  collidesWith: collidesWith,
425  findNewContacts: findNewContacts);
426  FarseerBody.Friction = limbParams.Friction;
427  FarseerBody.Restitution = limbParams.Restitution;
428  FarseerBody.AngularDamping = limbParams.AngularDamping;
429  FarseerBody.UserData = this;
430  _collisionCategories = collisionCategory;
431  _collidesWith = collidesWith;
432  SetTransformIgnoreContacts(position, 0.0f);
433  LastSentPosition = position;
434  list.Add(this);
435  }
436 
437  public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f, float? forceDensity = null, Category collisionCategory = Physics.CollisionItem, Category collidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform, bool findNewContacts = true)
438  {
439  float radius = ConvertUnits.ToSimUnits(element.GetAttributeFloat("radius", 0.0f)) * scale;
440  float height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 0.0f)) * scale;
441  float width = ConvertUnits.ToSimUnits(element.GetAttributeFloat("width", 0.0f)) * scale;
442  density = Math.Max(forceDensity ?? element.GetAttributeFloat("density", Physics.NeutralDensity), MinDensity);
443  Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType);
444  if (element.GetAttributeBool("ignorecollision", false))
445  {
446  _collisionCategories = Category.None;
447  _collidesWith = Category.None;
448  }
449  else
450  {
451  _collisionCategories = collisionCategory;
452  _collidesWith = collidesWith;
453  }
454  CreateBody(width, height, radius, density, bodyType, _collisionCategories, _collidesWith, findNewContacts);
455  FarseerBody.Friction = element.GetAttributeFloat("friction", 0.5f);
456  FarseerBody.Restitution = element.GetAttributeFloat("restitution", 0.05f);
457  FarseerBody.UserData = this;
458  SetTransformIgnoreContacts(position, 0.0f);
459  LastSentPosition = position;
460  list.Add(this);
461  }
462 
463  private void CreateBody(float width, float height, float radius, float density, BodyType bodyType, Category collisionCategory, Category collidesWith, bool findNewContacts = true)
464  {
465  if (IsValidShape(radius, height, width))
466  {
467  bodyShape = DefineBodyShape(radius, width, height);
468  switch (bodyShape)
469  {
470  case Shape.Capsule:
471  FarseerBody = GameMain.World.CreateCapsule(height, radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts); ;
472  break;
473  case Shape.HorizontalCapsule:
474  FarseerBody = GameMain.World.CreateCapsuleHorizontal(width, radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts);
475  break;
476  case Shape.Circle:
477  FarseerBody = GameMain.World.CreateCircle(radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts);
478  break;
479  case Shape.Rectangle:
480  FarseerBody = GameMain.World.CreateRectangle(width, height, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts);
481  break;
482  default:
483  throw new NotImplementedException(bodyShape.ToString());
484  }
485  }
486  else
487  {
488  DebugConsole.ThrowError("Invalid physics body dimensions (width: " + width + ", height: " + height + ", radius: " + radius + ")");
489  }
490  Width = width;
491  Height = height;
492  Radius = radius;
493  _collisionCategories = collisionCategory;
494  _collidesWith = collidesWith;
495  }
496 
504  public Vector2 GetLocalFront(float? spritesheetRotation = null)
505  {
506  Vector2 pos;
507  switch (bodyShape)
508  {
509  case Shape.Capsule:
510  pos = new Vector2(0.0f, Height / 2 + Radius);
511  break;
512  case Shape.HorizontalCapsule:
513  pos = new Vector2(Width / 2 + Radius, 0.0f);
514  break;
515  case Shape.Circle:
516  pos = new Vector2(0.0f, Radius);
517  break;
518  case Shape.Rectangle:
519  pos = Height > Width ? new Vector2(0, Height / 2) : new Vector2(Width / 2, 0);
520  break;
521  default:
522  throw new NotImplementedException();
523  }
524  return spritesheetRotation.HasValue ? Vector2.Transform(pos, Matrix.CreateRotationZ(-spritesheetRotation.Value)) : pos;
525  }
526 
527  public float GetMaxExtent()
528  {
529  switch (bodyShape)
530  {
531  case Shape.Capsule:
532  return Height / 2 + Radius;
533  case Shape.HorizontalCapsule:
534  return Width / 2 + Radius;
535  case Shape.Circle:
536  return Radius;
537  case Shape.Rectangle:
538  return new Vector2(Width * 0.5f, Height * 0.5f).Length();
539  default:
540  throw new NotImplementedException();
541  }
542  }
543 
544  public Vector2 GetSize()
545  {
546  switch (bodyShape)
547  {
548  case Shape.Capsule:
549  return new Vector2(Radius * 2, Height + Radius * 2);
550  case Shape.HorizontalCapsule:
551  return new Vector2(Width + Radius * 2, Radius * 2);
552  case Shape.Circle:
553  return new Vector2(Radius * 2);
554  case Shape.Rectangle:
555  return new Vector2(Width, Height);
556  default:
557  throw new NotImplementedException();
558  }
559  }
560 
561  public void SetSize(Vector2 size)
562  {
563  switch (bodyShape)
564  {
565  case Shape.Capsule:
566  Radius = Math.Max(size.X / 2, 0);
567  Height = Math.Max(size.Y - size.X, 0);
568  Width = 0;
569  break;
570  case Shape.HorizontalCapsule:
571  Radius = Math.Max(size.Y / 2, 0);
572  Width = Math.Max(size.X - size.Y, 0);
573  Height = 0;
574  break;
575  case Shape.Circle:
576  Radius = Math.Max(Math.Min(size.X, size.Y) / 2, 0);
577  Width = 0;
578  Height = 0;
579  break;
580  case Shape.Rectangle:
581  Width = Math.Max(size.X, 0);
582  Height = Math.Max(size.Y, 0);
583  Radius = 0;
584  break;
585  default:
586  throw new NotImplementedException();
587  }
588 #if CLIENT
589  bodyShapeTexture = null;
590 #endif
591  }
592 
593  public bool IsValidValue(float value, string valueName, float minValue = float.MinValue, float maxValue = float.MaxValue)
594  {
595  if (!MathUtils.IsValid(value) || value < minValue || value > maxValue)
596  {
597  string userData = UserData == null ? "null" : UserData.ToString();
598  string errorMsg =
599  "Attempted to apply invalid " + valueName +
600  " to a physics body (userdata: " + userData +
601  "), value: " + value;
602  if (GameMain.NetworkMember != null)
603  {
604  errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
605  }
606  errorMsg += "\n" + Environment.StackTrace.CleanupStackTrace();
607 
608  if (GameSettings.CurrentConfig.VerboseLogging) DebugConsole.ThrowError(errorMsg);
609  GameAnalyticsManager.AddErrorEventOnce(
610  "PhysicsBody.SetPosition:InvalidPosition" + userData,
611  GameAnalyticsManager.ErrorSeverity.Error,
612  errorMsg);
613  return false;
614  }
615  return true;
616  }
617 
618  private bool IsValidValue(Vector2 value, string valueName, float minValue = float.MinValue, float maxValue = float.MaxValue)
619  {
620  if (!MathUtils.IsValid(value) ||
621  (value.X < minValue || value.Y < minValue) ||
622  (value.X > maxValue || value.Y > maxValue))
623  {
624  string userData = UserData == null ? "null" : UserData.ToString();
625  string errorMsg =
626  "Attempted to apply invalid " + valueName +
627  " to a physics body (userdata: " + userData +
628  "), value: " + value;
629  if (GameMain.NetworkMember != null)
630  {
631  errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
632  }
633  errorMsg += "\n" + Environment.StackTrace.CleanupStackTrace();
634 
635  if (GameSettings.CurrentConfig.VerboseLogging) DebugConsole.ThrowError(errorMsg);
636  GameAnalyticsManager.AddErrorEventOnce(
637  "PhysicsBody.SetPosition:InvalidPosition" + userData,
638  GameAnalyticsManager.ErrorSeverity.Error,
639  errorMsg);
640  return false;
641  }
642  return true;
643  }
644 
645  public void ResetDynamics()
646  {
647  FarseerBody.ResetDynamics();
648  }
649 
650  public void ApplyLinearImpulse(Vector2 impulse)
651  {
652  if (!IsValidValue(impulse / FarseerBody.Mass, "new velocity", -1000f, 1000f)) return;
653  if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
654 
655  FarseerBody.ApplyLinearImpulse(impulse);
656  }
657 
661  public void ApplyLinearImpulse(Vector2 impulse, float maxVelocity)
662  {
663  if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
664  if (!IsValidValue(maxVelocity, "max velocity")) return;
665 
666  Vector2 velocityAddition = impulse / Mass;
667  Vector2 newVelocity = FarseerBody.LinearVelocity + velocityAddition;
668  float newSpeedSqr = newVelocity.LengthSquared();
669  if (newSpeedSqr > maxVelocity * maxVelocity)
670  {
671  newVelocity = newVelocity.ClampLength(maxVelocity);
672  }
673 
674  if (!IsValidValue((newVelocity - FarseerBody.LinearVelocity), "new velocity", -1000.0f, 1000.0f)) return;
675 
676  FarseerBody.ApplyLinearImpulse((newVelocity - FarseerBody.LinearVelocity) * Mass);
677  }
678 
679  public void ApplyLinearImpulse(Vector2 impulse, Vector2 point)
680  {
681  if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
682  if (!IsValidValue(point, "point")) return;
683  if (!IsValidValue(impulse / FarseerBody.Mass, "new velocity", -1000.0f, 1000.0f)) return;
684  FarseerBody.ApplyLinearImpulse(impulse, point);
685  }
686 
690  public void ApplyLinearImpulse(Vector2 impulse, Vector2 point, float maxVelocity)
691  {
692  if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
693  if (!IsValidValue(point, "point")) return;
694  if (!IsValidValue(maxVelocity, "max velocity")) return;
695 
696  Vector2 velocityAddition = impulse / Mass;
697  Vector2 newVelocity = FarseerBody.LinearVelocity + velocityAddition;
698  float newSpeedSqr = newVelocity.LengthSquared();
699  if (newSpeedSqr > maxVelocity * maxVelocity)
700  {
701  newVelocity = newVelocity.ClampLength(maxVelocity);
702  }
703 
704  if (!IsValidValue((newVelocity - FarseerBody.LinearVelocity), "new velocity", -1000.0f, 1000.0f)) return;
705 
706  FarseerBody.ApplyLinearImpulse((newVelocity - FarseerBody.LinearVelocity) * Mass, point);
707  FarseerBody.AngularVelocity = MathHelper.Clamp(
708  FarseerBody.AngularVelocity,
709  -NetConfig.MaxPhysicsBodyAngularVelocity,
710  NetConfig.MaxPhysicsBodyAngularVelocity);
711  }
712 
713  public void ApplyForce(Vector2 force, float maxVelocity = NetConfig.MaxPhysicsBodyVelocity)
714  {
715  if (!IsValidValue(maxVelocity, "max velocity")) { return; }
716 
717  Vector2 velocityAddition = force / Mass * (float)Timing.Step;
718  Vector2 newVelocity = FarseerBody.LinearVelocity + velocityAddition;
719 
720  float newSpeedSqr = newVelocity.LengthSquared();
721  if (newSpeedSqr > maxVelocity * maxVelocity && Vector2.Dot(FarseerBody.LinearVelocity, force) > 0.0f)
722  {
723  float newSpeed = (float)Math.Sqrt(newSpeedSqr);
724  float maxVelAddition = maxVelocity - newSpeed;
725  if (maxVelAddition <= 0.0f) { return; }
726  force = velocityAddition.ClampLength(maxVelAddition) * Mass / (float)Timing.Step;
727  }
728 
729  if (!IsValidValue(force, "clamped force", -1e10f, 1e10f)) { return; }
730  FarseerBody.ApplyForce(force);
731  }
732 
733  public void ApplyForce(Vector2 force, Vector2 point)
734  {
735  if (!IsValidValue(force, "force", -1e10f, 1e10f)) { return; }
736  if (!IsValidValue(point, "point")) { return; }
737  FarseerBody.ApplyForce(force, point);
738  }
739 
740  public void ApplyTorque(float torque)
741  {
742  if (!IsValidValue(torque, "torque")) { return; }
743  FarseerBody.ApplyTorque(torque);
744  }
745 
746  public bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform = true)
747  {
748  System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition));
749  System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f);
750  System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f);
751 
752  if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return false; }
753  if (!IsValidValue(rotation, "rotation")) { return false; }
754 
755  FarseerBody.SetTransform(simPosition, rotation);
756  if (setPrevTransform) { SetPrevTransform(simPosition, rotation); }
757  return true;
758  }
759 
760  public bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform = true)
761  {
762  System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition));
763  System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f);
764  System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f);
765 
766  if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return false; }
767  if (!IsValidValue(rotation, "rotation")) { return false; }
768 
769  FarseerBody.SetTransformIgnoreContacts(ref simPosition, rotation);
770  if (setPrevTransform) { SetPrevTransform(simPosition, rotation); }
771  return true;
772  }
773 
774  public void SetPrevTransform(Vector2 simPosition, float rotation)
775  {
776 #if DEBUG
777  if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return; }
778  if (!IsValidValue(rotation, "rotation")) { return; }
779 #endif
780  prevPosition = simPosition;
781  prevRotation = rotation;
782  }
783 
784  public void MoveToTargetPosition(bool lerp = true)
785  {
786  if (targetPosition == null) { return; }
787 
788  if (lerp)
789  {
790  if (Vector2.DistanceSquared((Vector2)targetPosition, FarseerBody.Position) < 10.0f * 10.0f)
791  {
792  drawOffset = -((Vector2)targetPosition - (FarseerBody.Position + drawOffset));
793  prevPosition = (Vector2)targetPosition;
794  }
795  else
796  {
797  drawOffset = Vector2.Zero;
798  }
799  if (targetRotation.HasValue)
800  {
801  rotationOffset = -MathUtils.GetShortestAngle(FarseerBody.Rotation + rotationOffset, targetRotation.Value);
802  }
803  }
804 
806  targetPosition = null;
807  targetRotation = null;
808  }
809 
810  public void MoveToPos(Vector2 simPosition, float force, Vector2? pullPos = null)
811  {
812  if (pullPos == null) { pullPos = FarseerBody.Position; }
813 
814  if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return; }
815  if (!IsValidValue(force, "force")) { return; }
816 
817  Vector2 vel = FarseerBody.LinearVelocity;
818  Vector2 deltaPos = simPosition - (Vector2)pullPos;
819  if (deltaPos.LengthSquared() > 100.0f * 100.0f)
820  {
821 #if DEBUG
822  DebugConsole.ThrowError("Attempted to move a physics body to an invalid position.\n" + Environment.StackTrace.CleanupStackTrace());
823 #endif
824  return;
825  }
826  deltaPos *= force;
827  ApplyLinearImpulse((deltaPos - vel * 0.5f) * FarseerBody.Mass, (Vector2)pullPos);
828  }
829 
833  public void ApplyWaterForces()
834  {
835  //buoyancy
836  Vector2 buoyancy = new Vector2(0, Mass * 9.6f);
837 
838  Vector2 dragForce = Vector2.Zero;
839 
840  float speedSqr = LinearVelocity.LengthSquared();
841  if (speedSqr > 0.00001f)
842  {
843  //drag
844  float speed = (float)Math.Sqrt(speedSqr);
845  Vector2 velDir = LinearVelocity / speed;
846 
847  float vel = speed * 2.0f;
848  float drag = vel * vel * Math.Max(Height + Radius * 2, Height);
849  dragForce = Math.Min(drag, Mass * 500.0f) * -velDir;
850  }
851 
852  ApplyForce(dragForce + buoyancy);
853  ApplyTorque(FarseerBody.AngularVelocity * FarseerBody.Mass * -0.08f);
854  }
855 
856  public void Update()
857  {
858  if (drawOffset.LengthSquared() < 0.01f)
859  {
861  }
862  drawOffset = NetConfig.InterpolateSimPositionError(drawOffset, PositionSmoothingFactor);
863  rotationOffset = NetConfig.InterpolateRotationError(rotationOffset);
865  {
866  if (smoothRotationSuppressionCounter > 0)
867  {
869  }
870  else
871  {
872  smoothRotationSuppressionCounter++;
873  }
874  }
875  }
876 
877  public void UpdateDrawPosition(bool interpolate = true)
878  {
879  if (interpolate)
880  {
881  drawPosition = Timing.Interpolate(prevPosition, FarseerBody.Position);
882  drawPosition = ConvertUnits.ToDisplayUnits(drawPosition + drawOffset);
883  drawRotation = Timing.InterpolateRotation(prevRotation, FarseerBody.Rotation) + rotationOffset;
884  }
885  else
886  {
887  drawPosition = prevPosition = ConvertUnits.ToDisplayUnits(FarseerBody.Position);
888  drawRotation = prevRotation = FarseerBody.Rotation;
889  drawOffset = Vector2.Zero;
890  drawRotation = 0.0f;
891  }
892  }
893 
894  public void CorrectPosition<T>(List<T> positionBuffer,
895  out Vector2 newPosition, out Vector2 newVelocity, out float newRotation, out float newAngularVelocity) where T : PosInfo
896  {
897  newVelocity = LinearVelocity;
898  newPosition = SimPosition;
899  newRotation = Rotation;
900  newAngularVelocity = AngularVelocity;
901 
902  while (positionBuffer.Count > 0 && positionBuffer[0].Timestamp < lastProcessedNetworkState)
903  {
904  positionBuffer.RemoveAt(0);
905  }
906 
907  if (positionBuffer.Count == 0) { return; }
908 
909  lastProcessedNetworkState = positionBuffer[0].Timestamp;
910 
911  newVelocity = positionBuffer[0].LinearVelocity;
912  newPosition = positionBuffer[0].Position;
913  newRotation = positionBuffer[0].Rotation ?? Rotation;
914  newAngularVelocity = positionBuffer[0].AngularVelocity ?? AngularVelocity;
915 
916  positionBuffer.RemoveAt(0);
917  }
918 
925  public void SmoothRotate(float targetRotation, float force = 10.0f, bool wrapAngle = true)
926  {
927  if (SuppressSmoothRotationCalls) { return; }
928  float nextAngle = FarseerBody.Rotation + FarseerBody.AngularVelocity * (float)Timing.Step;
929  float angle = wrapAngle ?
930  MathUtils.GetShortestAngle(nextAngle, targetRotation) :
931  MathHelper.Clamp(targetRotation - nextAngle, -MathHelper.Pi, MathHelper.Pi);
932  float torque = angle * 60.0f * (force / 100.0f);
933 
934  if (FarseerBody.BodyType == BodyType.Kinematic)
935  {
936  if (!IsValidValue(torque, "torque")) { return; }
937  FarseerBody.AngularVelocity = torque;
938  }
939  else
940  {
941  ApplyTorque(FarseerBody.Mass * torque);
942  }
943  }
944 
949  public float WrapAngleToSameNumberOfRevolutions(float angle)
950  {
951  if (float.IsInfinity(angle)) { return angle; }
952  while (Rotation - angle > MathHelper.TwoPi)
953  {
954  angle += MathHelper.TwoPi;
955  }
956  while (Rotation - angle < -MathHelper.TwoPi)
957  {
958  angle -= MathHelper.TwoPi;
959  }
960  return angle;
961  }
962 
963  public void Remove()
964  {
965  list.Remove(this);
966  GameMain.World.Remove(FarseerBody);
967 
968  Removed = true;
969 
970  DisposeProjSpecific();
971  }
972 
973  public static void RemoveAll()
974  {
975  for (int i = list.Count - 1; i >= 0; i--)
976  {
977  list[i].Remove();
978  }
979  System.Diagnostics.Debug.Assert(list.Count == 0);
980  }
981 
982  public static bool IsValidShape(float radius, float height, float width) => radius > 0 || (height > 0 && width > 0);
983 
984  public static Shape DefineBodyShape(float radius, float width, float height)
985  {
986  Shape bodyShape;
987  if (width <= 0 && height <= 0 && radius > 0)
988  {
989  bodyShape = Shape.Circle;
990  }
991  else if (radius > 0)
992  {
993  if (width > height)
994  {
995  bodyShape = Shape.HorizontalCapsule;
996  }
997  else
998  {
999  bodyShape = Shape.Capsule;
1000  }
1001  }
1002  else
1003  {
1004  bodyShape = Shape.Rectangle;
1005  }
1006  return bodyShape;
1007  }
1008 
1009  partial void DisposeProjSpecific();
1010 
1011  }
1012 }
static World World
Definition: GameMain.cs:105
static NetworkMember NetworkMember
Definition: GameMain.cs:190
PhysicsBody(XElement element, Vector2 position, float scale=1.0f, float? forceDensity=null, Category collisionCategory=Physics.CollisionItem, Category collidesWith=Physics.CollisionWall|Physics.CollisionLevel|Physics.CollisionPlatform, bool findNewContacts=true)
PhysicsBody(LimbParams lParams, bool findNewContacts=true)
static float TransformRotation(float rot, float dir)
static bool IsValidShape(float radius, float height, float width)
void ApplyLinearImpulse(Vector2 impulse, Vector2 point)
void CorrectPosition< T >(List< T > positionBuffer, out Vector2 newPosition, out Vector2 newVelocity, out float newRotation, out float newAngularVelocity)
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
PhysicsBody(ColliderParams colliderParams, Vector2 position, bool findNewContacts=true)
bool SuppressSmoothRotationCalls
Ignore rotation calls for the rest of this and the next update. Automatically disabled after that....
void ApplyLinearImpulse(Vector2 impulse, Vector2 point, float maxVelocity)
Apply an impulse to the body without increasing it's velocity above a specific limit.
PhysicsBody(LimbParams limbParams, Vector2 position, bool findNewContacts=true)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
bool IsValidValue(float value, string valueName, float minValue=float.MinValue, float maxValue=float.MaxValue)
PhysicsBody(float width, float height, float radius, float density, BodyType bodyType, Category collisionCategory, Category collidesWith, bool findNewContacts=true)
Vector2 GetLocalFront(float? spritesheetRotation=null)
Returns the farthest point towards the forward of the body. For capsules and circles,...
float TransformRotation(float rotation)
void ApplyWaterForces()
Applies buoyancy, drag and angular drag caused by water
void MoveToPos(Vector2 simPosition, float force, Vector2? pullPos=null)
PhysicsBody(ColliderParams cParams, bool findNewContacts=true)
static Shape DefineBodyShape(float radius, float width, float height)
void ApplyLinearImpulse(Vector2 impulse, float maxVelocity)
Apply an impulse to the body without increasing it's velocity above a specific limit.
float TransformedRotation
Takes flipping (Dir) into account.
bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void SetPrevTransform(Vector2 simPosition, float rotation)
float WrapAngleToSameNumberOfRevolutions(float angle)
Wraps the angle so it has "has the same number of revolutions" as this body, i.e. that the angles are...
void SmoothRotate(float targetRotation, float force=10.0f, bool wrapAngle=true)
Rotate the body towards the target rotation in the "shortest direction", taking into account the curr...
Vector2 DrawPositionOffset
Offset of the DrawPosition from the Position (i.e. how much the interpolated draw position is offset ...
PhysicsBody(XElement element, float scale=1.0f, bool findNewContacts=true)
PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID)
PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID, float time)
PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, float time)
void Translate(Vector2 posAmount, float rotationAmount)
static Submarine FindContainingInLocalCoordinates(Vector2 position, float inflate=500.0f)
Finds the sub whose borders contain the position. Note that this method uses the "actual" position of...