A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using osuTK;
6using osu.Framework.Graphics;
7
8namespace osu.Framework.Physics
9{
10 /// <summary>
11 /// Contains physical state and methods necessary for rigid body simulation.
12 /// </summary>
13 public interface IRigidBody : IDrawable
14 {
15 /// <summary>
16 /// The <see cref="Drawable"/> which is currently performing a simulation on this <see cref="IRigidBody"/>.
17 /// </summary>
18 Drawable Simulation { get; set; }
19
20 /// <summary>
21 /// Controls how elastic the material is. A value of 1 means perfect elasticity
22 /// (kinetic energy is fully preserved). A value of 0 means all energy is absorbed
23 /// on collision, i.e. no rebound occurs at all.
24 /// </summary>
25 float Restitution { get; set; }
26
27 /// <summary>
28 /// How much friction happens between objects.
29 /// </summary>
30 float FrictionCoefficient { get; set; }
31
32 Vector2 Centre { get; set; }
33
34 float RotationRadians { get; set; }
35
36 float Mass { get; set; }
37
38 Vector2 Velocity { get; }
39
40 Vector2 Momentum { get; set; }
41
42 float AngularVelocity { get; }
43
44 float AngularMomentum { get; set; }
45
46 float MomentOfInertia { get; }
47
48 /// <summary>
49 /// Total velocity at a given location. Includes angular velocity.
50 /// </summary>
51 Vector2 VelocityAt(Vector2 pos);
52
53 /// <summary>
54 /// Applies a given impulse attacking at a given position.
55 /// </summary>
56 void ApplyImpulse(Vector2 impulse, Vector2 pos);
57
58 /// <summary>
59 /// Checks for and records all collisions with another body. If collisions were found,
60 /// their aggregate is handled.
61 /// </summary>
62 bool CheckAndHandleCollisionWith(IRigidBody other);
63
64 /// <summary>
65 /// Performs an integration step over time. More precisely, updates the
66 /// physical state as dependent on time according to the forces and torques
67 /// acting on this body.
68 /// </summary>
69 void Integrate(Vector2 force, float torque, float dt);
70
71 /// <summary>
72 /// Reads the positional and rotational state of this rigid body from its source.
73 /// </summary>
74 void ReadState();
75
76 /// <summary>
77 /// Applies the positional and rotational state of this rigid body to its source.
78 /// </summary>
79 void ApplyState();
80
81 /// <summary>
82 /// Whether the given screen-space position is contained within the rigid body.
83 /// </summary>
84 bool BodyContains(Vector2 screenSpacePos);
85 }
86
87 /// <summary>
88 /// Helper extension methods operating on <see cref="IRigidBody"/>.
89 /// </summary>
90 public static class RigidBodyExtensions
91 {
92 /// <summary>
93 /// Helper function for code brevity in <see cref="ComputeImpulse(IRigidBody, IRigidBody, Vector2, Vector2)"/>.
94 /// Can be moved into the function as a nested method once C# 7 is out.
95 /// </summary>
96 public static float ImpulseDenominator(this IRigidBody body, Vector2 pos, Vector2 normal)
97 {
98 Vector2 diff = pos - body.Centre;
99 float perpDot = Vector2.Dot(normal, diff.PerpendicularRight);
100 return 1.0f / body.Mass + perpDot * perpDot / body.MomentOfInertia;
101 }
102
103 /// <summary>
104 /// Computes the impulse of a collision of 2 rigid bodies, given the other body, the impact position,
105 /// and the surface normal of this body at the impact position.
106 /// </summary>
107 public static Vector2 ComputeImpulse(this IRigidBody body, IRigidBody other, Vector2 pos, Vector2 normal)
108 {
109 Vector2 vrel = body.VelocityAt(pos) - other.VelocityAt(pos);
110 float vrelOrtho = -Vector2.Dot(vrel, normal);
111
112 // We don't want to consider collisions where objects move away from each other.
113 // (Or with negligible velocity. Let repulsive forces handle these.)
114 if (vrelOrtho > -0.001f)
115 return Vector2.Zero;
116
117 float impulseMagnitude = -(1.0f + body.Restitution) * vrelOrtho;
118 impulseMagnitude /= body.ImpulseDenominator(pos, normal) + other.ImpulseDenominator(pos, normal);
119
120 //impulseMagnitude = Math.Max(impulseMagnitude - 0.01f, 0.0f);
121
122 Vector2 impulse = -normal * impulseMagnitude;
123
124 // Add "friction" to the impulse. We arbitrarily reduce the planar velocity relative to the impulse magnitude.
125 Vector2 vrelPlanar = vrel + vrelOrtho * normal;
126 float vrelPlanarLength = vrelPlanar.Length;
127 if (vrelPlanarLength > 0)
128 impulse -= vrelPlanar * Math.Min(impulseMagnitude * 0.05f * body.FrictionCoefficient * other.FrictionCoefficient / vrelPlanarLength, body.Mass);
129
130 return impulse;
131 }
132 }
133}