3 using FarseerPhysics.Dynamics;
4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
40 public readonly UInt16
ID;
42 public PosInfo(Vector2 pos,
float? rotation, Vector2 linearVelocity,
float? angularVelocity,
float time)
43 : this(pos, rotation, linearVelocity, angularVelocity, 0, time)
47 public PosInfo(Vector2 pos,
float? rotation, Vector2 linearVelocity,
float? angularVelocity, UInt16
ID)
48 : this(pos, rotation, linearVelocity, angularVelocity,
ID, 0.0f)
52 protected PosInfo(Vector2 pos,
float? rotation, Vector2 linearVelocity,
float? angularVelocity, UInt16
ID,
float time)
74 Position += ConvertUnits.ToSimUnits(sub.Position);
78 public void Translate(Vector2 posAmount,
float rotationAmount)
94 private static readonly List<PhysicsBody> list =
new List<PhysicsBody>();
95 public static List<PhysicsBody>
List
106 private Vector2 drawPosition;
107 private float drawRotation;
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; }
126 private readonly
float density;
132 private Vector2 drawOffset;
133 private float rotationOffset;
135 private float lastProcessedNetworkState;
141 get {
return bodyShape; }
155 if (!
IsValidValue(value.Value,
"target position", -1e5f, 1e5f))
return;
157 MathHelper.Clamp(((Vector2)value).X, -10000.0f, 10000.0f),
158 MathHelper.Clamp(((Vector2)value).Y, -10000.0f, 10000.0f));
174 if (!
IsValidValue(value.Value,
"target rotation"))
return;
188 get {
return drawRotation; }
199 private bool isEnabled =
true;
200 private bool isPhysEnabled =
true;
204 get {
return isEnabled; }
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);
222 DebugConsole.NewMessage(
"Entity \"" + entity.ToString() +
"\" removed!", Color.Red);
233 isPhysEnabled = value;
248 get {
return ConvertUnits.ToDisplayUnits(
FarseerBody.Position); }
273 public static float TransformRotation(
float rot,
float dir) => dir < 0 ? rot - MathHelper.Pi : rot;
280 if (!
IsValidValue(value,
"velocity", -1000.0f, 1000.0f))
return;
290 if (!
IsValidValue(value,
"angular velocity", -1000f, 1000f))
return;
302 get {
return density; }
324 private Category _collisionCategories;
330 _collisionCategories = value;
335 return _collisionCategories;
339 private Category _collidesWith;
344 _collidesWith = value;
349 return _collidesWith;
358 get => _suppressSmoothRotationCalls;
361 _suppressSmoothRotationCalls = value;
362 smoothRotationSuppressionCounter = 0;
366 private bool _suppressSmoothRotationCalls;
367 private int smoothRotationSuppressionCounter;
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) { }
373 public PhysicsBody(
float width,
float height,
float radius,
float density,
BodyType bodyType, Category collisionCategory, Category collidesWith,
bool findNewContacts =
true)
376 CreateBody(width, height, radius, density, bodyType, collisionCategory, collidesWith, findNewContacts);
389 public PhysicsBody(ColliderParams colliderParams, Vector2 position,
bool findNewContacts =
true)
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,
408 public PhysicsBody(LimbParams limbParams, Vector2 position,
bool findNewContacts =
true)
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);
415 Category collisionCategory = Physics.CollisionCharacter;
416 Category collidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
417 if (limbParams.IgnoreCollisions)
419 collisionCategory = Category.None;
420 collidesWith = Category.None;
422 CreateBody(width, height, radius, density,
BodyType.Dynamic,
423 collisionCategory: collisionCategory,
424 collidesWith: collidesWith,
425 findNewContacts: findNewContacts);
428 FarseerBody.AngularDamping = limbParams.AngularDamping;
430 _collisionCategories = collisionCategory;
431 _collidesWith = collidesWith;
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)
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))
446 _collisionCategories = Category.None;
447 _collidesWith = Category.None;
451 _collisionCategories = collisionCategory;
452 _collidesWith = collidesWith;
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);
463 private void CreateBody(
float width,
float height,
float radius,
float density,
BodyType bodyType, Category collisionCategory, Category collidesWith,
bool findNewContacts =
true)
471 FarseerBody =
GameMain.
World.CreateCapsule(height, radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts); ;
473 case Shape.HorizontalCapsule:
474 FarseerBody =
GameMain.
World.CreateCapsuleHorizontal(width, radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts);
477 FarseerBody =
GameMain.
World.CreateCircle(radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts);
479 case Shape.Rectangle:
480 FarseerBody =
GameMain.
World.CreateRectangle(width, height, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts);
483 throw new NotImplementedException(bodyShape.ToString());
488 DebugConsole.ThrowError(
"Invalid physics body dimensions (width: " + width +
", height: " + height +
", radius: " + radius +
")");
493 _collisionCategories = collisionCategory;
494 _collidesWith = collidesWith;
512 case Shape.HorizontalCapsule:
516 pos =
new Vector2(0.0f,
Radius);
518 case Shape.Rectangle:
522 throw new NotImplementedException();
524 return spritesheetRotation.HasValue ? Vector2.Transform(pos, Matrix.CreateRotationZ(-spritesheetRotation.Value)) : pos;
533 case Shape.HorizontalCapsule:
537 case Shape.Rectangle:
538 return new Vector2(
Width * 0.5f,
Height * 0.5f).Length();
540 throw new NotImplementedException();
550 case Shape.HorizontalCapsule:
553 return new Vector2(
Radius * 2);
554 case Shape.Rectangle:
557 throw new NotImplementedException();
566 Radius = Math.Max(size.X / 2, 0);
567 Height = Math.Max(size.Y - size.X, 0);
570 case Shape.HorizontalCapsule:
571 Radius = Math.Max(size.Y / 2, 0);
572 Width = Math.Max(size.X - size.Y, 0);
576 Radius = Math.Max(Math.Min(size.X, size.Y) / 2, 0);
580 case Shape.Rectangle:
581 Width = Math.Max(size.X, 0);
582 Height = Math.Max(size.Y, 0);
586 throw new NotImplementedException();
589 bodyShapeTexture =
null;
593 public bool IsValidValue(
float value,
string valueName,
float minValue =
float.MinValue,
float maxValue =
float.MaxValue)
595 if (!MathUtils.IsValid(value) || value < minValue || value > maxValue)
599 "Attempted to apply invalid " + valueName +
600 " to a physics body (userdata: " + userData +
601 "), value: " + value;
606 errorMsg +=
"\n" + Environment.StackTrace.CleanupStackTrace();
608 if (GameSettings.CurrentConfig.VerboseLogging) DebugConsole.ThrowError(errorMsg);
609 GameAnalyticsManager.AddErrorEventOnce(
610 "PhysicsBody.SetPosition:InvalidPosition" + userData,
611 GameAnalyticsManager.ErrorSeverity.Error,
618 private bool IsValidValue(Vector2 value,
string valueName,
float minValue =
float.MinValue,
float maxValue =
float.MaxValue)
620 if (!MathUtils.IsValid(value) ||
621 (value.X < minValue || value.Y < minValue) ||
622 (value.X > maxValue || value.Y > maxValue))
626 "Attempted to apply invalid " + valueName +
627 " to a physics body (userdata: " + userData +
628 "), value: " + value;
633 errorMsg +=
"\n" + Environment.StackTrace.CleanupStackTrace();
635 if (GameSettings.CurrentConfig.VerboseLogging) DebugConsole.ThrowError(errorMsg);
636 GameAnalyticsManager.AddErrorEventOnce(
637 "PhysicsBody.SetPosition:InvalidPosition" + userData,
638 GameAnalyticsManager.ErrorSeverity.Error,
653 if (!
IsValidValue(impulse,
"impulse", -1e10f, 1e10f))
return;
663 if (!
IsValidValue(impulse,
"impulse", -1e10f, 1e10f))
return;
666 Vector2 velocityAddition = impulse /
Mass;
667 Vector2 newVelocity =
FarseerBody.LinearVelocity + velocityAddition;
668 float newSpeedSqr = newVelocity.LengthSquared();
669 if (newSpeedSqr > maxVelocity * maxVelocity)
671 newVelocity = newVelocity.ClampLength(maxVelocity);
681 if (!
IsValidValue(impulse,
"impulse", -1e10f, 1e10f))
return;
692 if (!
IsValidValue(impulse,
"impulse", -1e10f, 1e10f))
return;
696 Vector2 velocityAddition = impulse /
Mass;
697 Vector2 newVelocity =
FarseerBody.LinearVelocity + velocityAddition;
698 float newSpeedSqr = newVelocity.LengthSquared();
699 if (newSpeedSqr > maxVelocity * maxVelocity)
701 newVelocity = newVelocity.ClampLength(maxVelocity);
709 -NetConfig.MaxPhysicsBodyAngularVelocity,
710 NetConfig.MaxPhysicsBodyAngularVelocity);
713 public void ApplyForce(Vector2 force,
float maxVelocity = NetConfig.MaxPhysicsBodyVelocity)
715 if (!
IsValidValue(maxVelocity,
"max velocity")) {
return; }
717 Vector2 velocityAddition = force /
Mass * (float)Timing.Step;
718 Vector2 newVelocity =
FarseerBody.LinearVelocity + velocityAddition;
720 float newSpeedSqr = newVelocity.LengthSquared();
721 if (newSpeedSqr > maxVelocity * maxVelocity && Vector2.Dot(
FarseerBody.LinearVelocity, force) > 0.0f)
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;
729 if (!
IsValidValue(force,
"clamped force", -1e10f, 1e10f)) {
return; }
735 if (!
IsValidValue(force,
"force", -1e10f, 1e10f)) {
return; }
746 public bool SetTransform(Vector2 simPosition,
float rotation,
bool setPrevTransform =
true)
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);
752 if (!
IsValidValue(simPosition,
"position", -1e10f, 1e10f)) {
return false; }
753 if (!
IsValidValue(rotation,
"rotation")) {
return false; }
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);
766 if (!
IsValidValue(simPosition,
"position", -1e10f, 1e10f)) {
return false; }
767 if (!
IsValidValue(rotation,
"rotation")) {
return false; }
769 FarseerBody.SetTransformIgnoreContacts(ref simPosition, rotation);
777 if (!
IsValidValue(simPosition,
"position", -1e10f, 1e10f)) {
return; }
797 drawOffset = Vector2.Zero;
810 public void MoveToPos(Vector2 simPosition,
float force, Vector2? pullPos =
null)
812 if (pullPos ==
null) { pullPos =
FarseerBody.Position; }
814 if (!
IsValidValue(simPosition,
"position", -1e10f, 1e10f)) {
return; }
818 Vector2 deltaPos = simPosition - (Vector2)pullPos;
819 if (deltaPos.LengthSquared() > 100.0f * 100.0f)
822 DebugConsole.ThrowError(
"Attempted to move a physics body to an invalid position.\n" + Environment.StackTrace.CleanupStackTrace());
836 Vector2 buoyancy =
new Vector2(0,
Mass * 9.6f);
838 Vector2 dragForce = Vector2.Zero;
841 if (speedSqr > 0.00001f)
844 float speed = (float)Math.Sqrt(speedSqr);
847 float vel = speed * 2.0f;
849 dragForce = Math.Min(drag,
Mass * 500.0f) * -velDir;
858 if (drawOffset.LengthSquared() < 0.01f)
863 rotationOffset = NetConfig.InterpolateRotationError(rotationOffset);
866 if (smoothRotationSuppressionCounter > 0)
872 smoothRotationSuppressionCounter++;
882 drawPosition = ConvertUnits.ToDisplayUnits(drawPosition + drawOffset);
889 drawOffset = Vector2.Zero;
895 out Vector2 newPosition, out Vector2 newVelocity, out
float newRotation, out
float newAngularVelocity) where T :
PosInfo
902 while (positionBuffer.Count > 0 && positionBuffer[0].Timestamp < lastProcessedNetworkState)
904 positionBuffer.RemoveAt(0);
907 if (positionBuffer.Count == 0) {
return; }
909 lastProcessedNetworkState = positionBuffer[0].Timestamp;
911 newVelocity = positionBuffer[0].LinearVelocity;
912 newPosition = positionBuffer[0].Position;
913 newRotation = positionBuffer[0].Rotation ??
Rotation;
914 newAngularVelocity = positionBuffer[0].AngularVelocity ??
AngularVelocity;
916 positionBuffer.RemoveAt(0);
929 float angle = wrapAngle ?
931 MathHelper.Clamp(
targetRotation - nextAngle, -MathHelper.Pi, MathHelper.Pi);
932 float torque = angle * 60.0f * (force / 100.0f);
951 if (
float.IsInfinity(angle)) {
return angle; }
952 while (
Rotation - angle > MathHelper.TwoPi)
954 angle += MathHelper.TwoPi;
956 while (
Rotation - angle < -MathHelper.TwoPi)
958 angle -= MathHelper.TwoPi;
970 DisposeProjSpecific();
975 for (
int i = list.Count - 1; i >= 0; i--)
979 System.Diagnostics.Debug.Assert(list.Count == 0);
982 public static bool IsValidShape(
float radius,
float height,
float width) => radius > 0 || (height > 0 && width > 0);
987 if (width <= 0 && height <= 0 && radius > 0)
989 bodyShape =
Shape.Circle;
995 bodyShape =
Shape.HorizontalCapsule;
999 bodyShape =
Shape.Capsule;
1004 bodyShape =
Shape.Rectangle;
1009 partial
void DisposeProjSpecific();
static NetworkMember NetworkMember
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)
void ApplyTorque(float torque)
void ApplyLinearImpulse(Vector2 impulse)
void ApplyForce(Vector2 force, Vector2 point)
void SetSize(Vector2 size)
static float TransformRotation(float rot, float dir)
static bool IsValidShape(float radius, float height, float width)
float? PositionSmoothingFactor
void ApplyLinearImpulse(Vector2 impulse, Vector2 point)
void MoveToTargetPosition(bool lerp=true)
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)
Category CollisionCategories
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 UpdateDrawPosition(bool interpolate=true)
const float DefaultAngularDamping
void SetPrevTransform(Vector2 simPosition, float rotation)
static List< PhysicsBody > List
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)
PhysicsBody(Body farseerBody)
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 TransformOutToInside(Submarine submarine)
void Translate(Vector2 posAmount, float rotationAmount)
void TransformInToOutside()
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...
override Vector2? Position