diff --git a/docs/docs/00_intro.md b/docs/docs/00_intro.md deleted file mode 100644 index ecda1793..00000000 --- a/docs/docs/00_intro.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -sidebar_position: 0 ---- - -# Jitter Physics 2 - -You will find tutorials and documentation here. Work in progress. You can help -by editing or adding new content 👋 diff --git a/docs/docs/02_documentation/img/contact.png b/docs/docs/02_documentation/img/contact.png deleted file mode 100644 index 0d98e164..00000000 Binary files a/docs/docs/02_documentation/img/contact.png and /dev/null differ diff --git a/docs/docs/02_documentation/img/filter.png b/docs/docs/02_documentation/img/filter.png deleted file mode 100644 index edb6fffa..00000000 Binary files a/docs/docs/02_documentation/img/filter.png and /dev/null differ diff --git a/docs/impress/collision.odp b/docs/impress/collision.odp deleted file mode 100644 index a649afd8..00000000 Binary files a/docs/impress/collision.odp and /dev/null differ diff --git a/docs/impress/contact.odp b/docs/impress/contact.odp deleted file mode 100644 index e2d3a343..00000000 Binary files a/docs/impress/contact.odp and /dev/null differ diff --git a/docs/impress/filter.odp b/docs/impress/filter.odp deleted file mode 100644 index 7ad39236..00000000 Binary files a/docs/impress/filter.odp and /dev/null differ diff --git a/src/Jitter2/Collision/DynamicTree.RayCast.cs b/src/Jitter2/Collision/DynamicTree.RayCast.cs index 509db356..5ceda1ed 100644 --- a/src/Jitter2/Collision/DynamicTree.RayCast.cs +++ b/src/Jitter2/Collision/DynamicTree.RayCast.cs @@ -38,7 +38,7 @@ public partial class DynamicTree public struct RayCastResult { public IDynamicTreeProxy Entity; - public float Fraction; + public float Lambda; public JVector Normal; public bool Hit; } @@ -82,12 +82,12 @@ public Ray(in JVector origin, in JVector direction) /// Direction of the ray. Does not have to be normalized. /// Optional pre-filter which allows to skip shapes in the detection. /// Optional post-filter which allows to skip detections. - /// The shape which was hit. + /// The shape which was hit. /// The normal of the surface where the ray hits. Zero if ray does not hit. - /// Distance from the origin to the ray hit point in units of the ray's directin. + /// Distance from the origin to the ray hit point in units of the ray's direction. /// True if the ray hits, false otherwise. public bool RayCast(JVector origin, JVector direction, RayCastFilterPre? pre, RayCastFilterPost? post, - out IDynamicTreeProxy? shape, out JVector normal, out float fraction) + out IDynamicTreeProxy? proxy, out JVector normal, out float lambda) { Ray ray = new(origin, direction) { @@ -95,27 +95,27 @@ public bool RayCast(JVector origin, JVector direction, RayCastFilterPre? pre, Ra FilterPost = post }; var result = QueryRay(ray); - shape = result.Entity; + proxy = result.Entity; normal = result.Normal; - fraction = result.Fraction; + lambda = result.Lambda; return result.Hit; } /// - /// Maximum fraction of the ray's length to consider for intersections. - public bool RayCast(JVector origin, JVector direction, float maxFraction, RayCastFilterPre? pre, RayCastFilterPost? post, - out IDynamicTreeProxy? shape, out JVector normal, out float fraction) + /// Maximum lambda of the ray's length to consider for intersections. + public bool RayCast(JVector origin, JVector direction, float maxLambda, RayCastFilterPre? pre, RayCastFilterPost? post, + out IDynamicTreeProxy? proxy, out JVector normal, out float lambda) { Ray ray = new(origin, direction) { FilterPre = pre, FilterPost = post, - Lambda = maxFraction + Lambda = maxLambda }; var result = QueryRay(ray); - shape = result.Entity; + proxy = result.Entity; normal = result.Normal; - fraction = result.Fraction; + lambda = result.Lambda; return result.Hit; } @@ -128,7 +128,7 @@ private RayCastResult QueryRay(in Ray ray) stack.Push(root); RayCastResult result = new(); - result.Fraction = ray.Lambda; + result.Lambda = ray.Lambda; while (stack.Count > 0) { @@ -143,10 +143,10 @@ private RayCastResult QueryRay(in Ray ray) if (ray.FilterPre != null && !ray.FilterPre(node.Proxy)) continue; Unsafe.SkipInit(out RayCastResult res); - res.Hit = irc.RayCast(ray.Origin, ray.Direction, out res.Normal, out res.Fraction); + res.Hit = irc.RayCast(ray.Origin, ray.Direction, out res.Normal, out res.Lambda); res.Entity = node.Proxy; - if (res.Hit && res.Fraction < result.Fraction) + if (res.Hit && res.Lambda < result.Lambda) { if (ray.FilterPost != null && !ray.FilterPost(res)) continue; result = res; @@ -161,8 +161,8 @@ private RayCastResult QueryRay(in Ray ray) bool lres = lnode.ExpandedBox.RayIntersect(ray.Origin, ray.Direction, out float enterl); bool rres = rnode.ExpandedBox.RayIntersect(ray.Origin, ray.Direction, out float enterr); - if (enterl > result.Fraction) lres = false; - if (enterr > result.Fraction) rres = false; + if (enterl > result.Lambda) lres = false; + if (enterr > result.Lambda) rres = false; if (lres && rres) { diff --git a/src/Jitter2/Collision/NarrowPhase/NarrowPhase.cs b/src/Jitter2/Collision/NarrowPhase/NarrowPhase.cs index 7dd54c78..142a5f5c 100644 --- a/src/Jitter2/Collision/NarrowPhase/NarrowPhase.cs +++ b/src/Jitter2/Collision/NarrowPhase/NarrowPhase.cs @@ -83,15 +83,13 @@ public bool PointTest(in ISupportMappable supportA, in JVector origin) return true; } - public bool RayCast(in ISupportMappable supportA, in JVector origin, in JVector direction, out float fraction, out JVector normal) + public bool RayCast(in ISupportMappable supportA, in JVector origin, in JVector direction, out float lambda, out JVector normal) { const float CollideEpsilon = 1e-4f; const int MaxIter = 34; normal = JVector.Zero; - fraction = float.PositiveInfinity; - - float lambda = 0.0f; + lambda = 0.0f; JVector r = direction; JVector x = origin; @@ -120,6 +118,7 @@ public bool RayCast(in ISupportMappable supportA, in JVector origin, in JVector if (VdotR >= -NumericEpsilon) { + lambda = float.PositiveInfinity; return false; } @@ -141,8 +140,6 @@ public bool RayCast(in ISupportMappable supportA, in JVector origin, in JVector converged: - fraction = lambda; - float nlen2 = normal.LengthSquared(); if (nlen2 > NumericEpsilon) @@ -154,7 +151,7 @@ public bool RayCast(in ISupportMappable supportA, in JVector origin, in JVector } public bool Sweep(ref MinkowskiDifference mkd, in JVector sweep, - out JVector p1, out JVector p2, out JVector normal, out float fraction) + out JVector p1, out JVector p2, out JVector normal, out float lambda) { const float CollideEpsilon = 1e-4f; const int MaxIter = 34; @@ -166,7 +163,7 @@ public bool Sweep(ref MinkowskiDifference mkd, in JVector sweep, JVector posB = mkd.PositionB; - fraction = 0.0f; + lambda = 0.0f; p1 = p2 = JVector.Zero; @@ -192,13 +189,13 @@ public bool Sweep(ref MinkowskiDifference mkd, in JVector sweep, if (VdotR >= -1e-12f) { - fraction = float.PositiveInfinity; + lambda = float.PositiveInfinity; return false; } - fraction -= VdotW / VdotR; + lambda -= VdotW / VdotR; - mkd.PositionB = posB + fraction * r; + mkd.PositionB = posB + lambda * r; normal = v; } @@ -709,7 +706,7 @@ public static bool PointTest(in ISupportMappable support, in JMatrix orientation /// The position of the shape in world space. /// The origin of the ray. /// The direction of the ray; normalization is not necessary. - /// Specifies the hit point of the ray, calculated as 'origin + fraction * direction'. + /// Specifies the hit point of the ray, calculated as 'origin + lambda * direction'. /// /// The normalized normal vector perpendicular to the surface, pointing outwards. If the ray does not /// hit, this parameter will be zero. @@ -717,13 +714,13 @@ public static bool PointTest(in ISupportMappable support, in JMatrix orientation /// Returns true if the ray intersects with the shape; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool RayCast(in ISupportMappable support, in JQuaternion orientation, - in JVector position, in JVector origin, in JVector direction, out float fraction, out JVector normal) + in JVector position, in JVector origin, in JVector direction, out float lambda, out JVector normal) { // rotate the ray into the reference frame of bodyA.. JVector tdirection = JVector.TransposedTransform(direction, orientation); JVector torigin = JVector.TransposedTransform(origin - position, orientation); - bool result = solver.RayCast(support, torigin, tdirection, out fraction, out normal); + bool result = solver.RayCast(support, torigin, tdirection, out lambda, out normal); // ..rotate back. JVector.Transform(normal, orientation, out normal); @@ -737,16 +734,16 @@ public static bool RayCast(in ISupportMappable support, in JQuaternion orientati /// The support function of the shape. /// The origin of the ray. /// The direction of the ray; normalization is not necessary. - /// Specifies the hit point of the ray, calculated as 'origin + fraction * direction'. + /// Specifies the hit point of the ray, calculated as 'origin + lambda * direction'. /// /// The normalized normal vector perpendicular to the surface, pointing outwards. If the ray does not /// hit, this parameter will be zero. /// /// Returns true if the ray intersects with the shape; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool RayCast(in ISupportMappable support, in JVector origin, in JVector direction, out float fraction, out JVector normal) + public static bool RayCast(in ISupportMappable support, in JVector origin, in JVector direction, out float lambda, out JVector normal) { - return solver.RayCast(support, origin, direction, out fraction, out normal); + return solver.RayCast(support, origin, direction, out lambda, out normal); } /// @@ -1068,14 +1065,14 @@ public static bool MPREPA(in ISupportMappable supportA, in ISupportMappable supp /// Zero if no hit is detected. /// Collision point on shapeB in world space at t = 0, where collision will occur. /// Zero if no hit is detected. - /// Time of impact. Infinity if no hit is detected. + /// Time of impact. Infinity if no hit is detected. /// True if the shapes hit, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Sweep(in ISupportMappable supportA, in ISupportMappable supportB, in JQuaternion orientationA, in JQuaternion orientationB, in JVector positionA, in JVector positionB, in JVector sweepA, in JVector sweepB, - out JVector pointA, out JVector pointB, out JVector normal, out float fraction) + out JVector pointA, out JVector pointB, out JVector normal, out float lambda) { Unsafe.SkipInit(out MinkowskiDifference mkd); @@ -1092,7 +1089,7 @@ public static bool Sweep(in ISupportMappable supportA, in ISupportMappable suppo JVector.ConjugatedTransform(sweep, orientationA, out sweep); // ..perform toi calculation - bool res = solver.Sweep(ref mkd, sweep, out pointA, out pointB, out normal, out fraction); + bool res = solver.Sweep(ref mkd, sweep, out pointA, out pointB, out normal, out lambda); if (!res) return false; @@ -1107,10 +1104,10 @@ public static bool Sweep(in ISupportMappable supportA, in ISupportMappable suppo // transform back from the relative velocities // This is where the collision will occur in world space: - // pointA += fraction * sweepA; - // pointB += fraction * sweepA; // sweepA is not a typo + // pointA += lambda * sweepA; + // pointB += lambda * sweepA; // sweepA is not a typo - pointB += fraction * (sweepA - sweepB); + pointB += lambda * (sweepA - sweepB); return true; } @@ -1121,12 +1118,12 @@ public static bool Sweep(in ISupportMappable supportA, in ISupportMappable suppo /// /// Collision point on shapeA in world space. Zero if no hit is detected. /// Collision point on shapeB in world space. Zero if no hit is detected. - /// Time of impact. Infinity if no hit is detected. + /// Time of impact. Infinity if no hit is detected. /// True if the shapes hit, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Sweep(in ISupportMappable supportA, in ISupportMappable supportB, in JQuaternion orientationB, in JVector positionB, in JVector sweepB, - out JVector pointA, out JVector pointB, out JVector normal, out float fraction) + out JVector pointA, out JVector pointB, out JVector normal, out float lambda) { Unsafe.SkipInit(out MinkowskiDifference mkd); @@ -1136,6 +1133,6 @@ public static bool Sweep(in ISupportMappable supportA, in ISupportMappable suppo mkd.OrientationB = orientationB; // ..perform toi calculation - return solver.Sweep(ref mkd, sweepB, out pointA, out pointB, out normal, out fraction); + return solver.Sweep(ref mkd, sweepB, out pointA, out pointB, out normal, out lambda); } } \ No newline at end of file diff --git a/src/Jitter2/Dynamics/RigidBody.cs b/src/Jitter2/Dynamics/RigidBody.cs index da13ec5b..0bc7f7a2 100644 --- a/src/Jitter2/Dynamics/RigidBody.cs +++ b/src/Jitter2/Dynamics/RigidBody.cs @@ -65,6 +65,9 @@ public sealed class RigidBody : IListIndex, IDebugDrawable public readonly ulong RigidBodyId; + private float restitution = 0.0f; + private float friction = 0.2f; + /// /// Due to performance considerations, the data used to simulate this body (e.g., velocity or position) /// is stored within a contiguous block of unmanaged memory. This refers to the raw memory location @@ -156,8 +159,59 @@ internal void RaiseEndCollide(Arbiter arbiter) internal JMatrix inverseInertia = JMatrix.Identity; internal float inverseMass = 1.0f; - public float Friction { get; set; } = 0.2f; - public float Restitution { get; set; } = 0.0f; + /// + /// Gets or sets the friction coefficient for this object. + /// + /// + /// The friction coefficient determines the resistance to sliding motion. + /// Higher values create more friction, while lower values allow easier sliding. + /// A typical value ranges between 0 (no friction) and 1 (maximum friction). + /// Default is 0.2. + /// + /// + /// Thrown if the value is not between 0 and 1. + /// + public float Friction + { + get => friction; + set + { + if (value < 0.0f || value > 1.0f) + { + throw new ArgumentOutOfRangeException(nameof(value), + "Friction must be between 0 and 1."); + } + + friction = value; + } + } + + /// + /// Gets or sets the restitution (bounciness) of this object. + /// + /// + /// The restitution value determines how much energy is retained after a collision, + /// with 0 representing an inelastic collision (no bounce) and 1 representing a perfectly elastic collision (full bounce). + /// Values between 0 and 1 create a partially elastic collision effect. + /// Default is 0.0. + /// + /// + /// Thrown if the value is not between 0 and 1. + /// + public float Restitution + { + get => restitution; + set + { + if (value < 0.0f || value > 1.0f) + { + throw new ArgumentOutOfRangeException(nameof(value), + "Restitution must be between 0 and 1."); + } + + restitution = value; + } + } private readonly int hashCode; diff --git a/src/Jitter2/SoftBodies/DynamicTreeCollisionFilter.cs b/src/Jitter2/SoftBodies/DynamicTreeCollisionFilter.cs index 35626f78..0500bd71 100644 --- a/src/Jitter2/SoftBodies/DynamicTreeCollisionFilter.cs +++ b/src/Jitter2/SoftBodies/DynamicTreeCollisionFilter.cs @@ -32,7 +32,7 @@ public static bool Filter(IDynamicTreeProxy proxyA, IDynamicTreeProxy proxyB) { if (proxyA is RigidBodyShape rbsA && proxyB is RigidBodyShape rbsB) { - if (rbsA.RigidBody == rbsB.RigidBody) return false; + return rbsA.RigidBody != rbsB.RigidBody; } else if (proxyA is SoftBodyShape softBodyShapeA && proxyB is SoftBodyShape softBodyShapeB) diff --git a/src/Jitter2/World.cs b/src/Jitter2/World.cs index 4971afab..b93b87ca 100644 --- a/src/Jitter2/World.cs +++ b/src/Jitter2/World.cs @@ -287,8 +287,8 @@ public void Clear() } /// - /// Removes the specified body from the world. This operation also automatically discards any associated contacts - /// and constraints. + /// Removes the specified body from the world. + /// This operation also automatically discards any associated contacts and constraints. /// public void Remove(RigidBody body) { diff --git a/src/JitterDemo/Demos/Player/Player.cs b/src/JitterDemo/Demos/Player/Player.cs index 65a2fb5e..918151eb 100644 --- a/src/JitterDemo/Demos/Player/Player.cs +++ b/src/JitterDemo/Demos/Player/Player.cs @@ -164,11 +164,11 @@ private bool CanJump(out RigidBody? floor, out JVector hitPoint) // ...or the more traditional way of using a raycast bool hit = world.RayCast(Body.Position, -JVector.UnitY, preFilter, null, - out floor, out JVector normal, out float fraction); + out floor, out JVector normal, out float lambda); - float delta = fraction - capsuleHalfHeight; + float delta = lambda - capsuleHalfHeight; - hitPoint = Body.Position - JVector.UnitY * fraction; + hitPoint = Body.Position - JVector.UnitY * lambda; return (hit && delta < 0.04f && floor != null); */ } diff --git a/src/JitterTests/CollisionTests.cs b/src/JitterTests/CollisionTests.cs index 526b8cfd..f1e2ac57 100644 --- a/src/JitterTests/CollisionTests.cs +++ b/src/JitterTests/CollisionTests.cs @@ -133,10 +133,10 @@ public void RayCast() SphereShape s1 = new(radius); bool hit = NarrowPhase.RayCast(s1, JQuaternion.CreateRotationX(0.32f), sp, - op, sp - op, out float fraction, out JVector normal); + op, sp - op, out float lambda, out JVector normal); JVector cn = JVector.Normalize(op - sp); // analytical normal - JVector hp = op + (sp - op) * fraction; // hit point + JVector hp = op + (sp - op) * lambda; // hit point Assert.That(hit); Assert.That(MathHelper.CloseToZero(normal - cn, 1e-6f)); @@ -157,20 +157,20 @@ public void SweepTest() bool hit = NarrowPhase.Sweep(s1, s2, rot, rot, new JVector(1, 1, 3), new JVector(11, 11, 3), sweep, -2.0f * sweep, - out JVector pA, out JVector pB, out JVector normal, out float fraction); + out JVector pA, out JVector pB, out JVector normal, out float lambda); Assert.That(hit); - float expectedFraction = (MathF.Sqrt(200.0f) - 1.0f) * (1.0f / 3.0f); + float expectedlambda = (MathF.Sqrt(200.0f) - 1.0f) * (1.0f / 3.0f); JVector expectedNormal = JVector.Normalize(new JVector(1, 1, 0)); - JVector expectedPoint = new JVector(1, 1, 3) + expectedNormal * (0.5f + expectedFraction); - JVector expectedPointA = expectedPoint - sweep * fraction; - JVector expectedPointB = expectedPoint + 2.0f * sweep * fraction; + JVector expectedPoint = new JVector(1, 1, 3) + expectedNormal * (0.5f + expectedlambda); + JVector expectedPointA = expectedPoint - sweep * lambda; + JVector expectedPointB = expectedPoint + 2.0f * sweep * lambda; Assert.That((normal - expectedNormal).LengthSquared(), Is.LessThan(1e-4f)); Assert.That((pA - expectedPointA).LengthSquared(), Is.LessThan(1e-4f)); Assert.That((pB - expectedPointB).LengthSquared(), Is.LessThan(1e-4f)); - Assert.That(MathF.Abs(fraction - expectedFraction), Is.LessThan(1e-4f)); + Assert.That(MathF.Abs(lambda - expectedlambda), Is.LessThan(1e-4f)); } [TestCase]