Files
opensim/OpenSim/Region/PhysicsModules/ubOde/ODECollision.cs

365 lines
12 KiB
C#

using OpenMetaverse;
using OpenSim.Region.PhysicsModules.SharedBase;
using System;
using System.Runtime.CompilerServices;
namespace OpenSim.Region.PhysicsModule.ubOde
{
public partial class ODEScene
{
internal bool CollideSpheres(Vector3 p1, float r1, Vector3 p2, float r2,
ref UBOdeNative.ContactGeom c)
{
float rsum = r1 + r2;
Vector3 delta = p1 - p2;
float d = delta.LengthSquared();
if (d >= rsum * rsum)
return false;
if (d < 1e-6f)
{
c.pos = Unsafe.As<Vector3, UBOdeNative.Vector3>(ref p1);
c.normal.X = 1;
c.normal.Y = 0;
c.normal.Z = 0;
c.depth = rsum;
return true;
}
d = MathF.Sqrt(d);
c.depth = rsum - d;
float k = r1 - 0.5f * c.depth;
delta *= 1.0f / d;
c.normal = Unsafe.As<Vector3, UBOdeNative.Vector3>(ref delta);
delta *= k;
delta = p1 - delta;
c.pos = Unsafe.As<Vector3, UBOdeNative.Vector3>(ref delta);
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 minusXRotateByShortQZ(Vector2 shortQuaternion)
{
float num = shortQuaternion.X + shortQuaternion.X;
return new(shortQuaternion.X * num - 1f, shortQuaternion.Y * num, 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool CollideSimpleChars(OdeCharacter p1, OdeCharacter p2, ref ContactPoint cp, ref int feet)
{
Vector3 tt;
tt.Z = p1._position.Z - p2._position.Z;
float absdz = Math.Abs(tt.Z);
float r = p1.CapsuleSizeZ + p2.CapsuleSizeZ;
if (absdz >= r)
return false;
tt.X = p1._position.X - p2._position.X;
tt.Y = p1._position.Y - p2._position.Y;
Vector3 v;
float r1, r2, rhsum, d;
float rsum = p1.CapsuleRadius + p2.CapsuleRadius;
r -= rsum;
if (absdz < r)
{
float hd = tt.X * tt.X + tt.Y * tt.Y;
if (hd < 1e-6f)
{
cp.Position = p1._position;
cp.Position.Z -= 0.5f * tt.Z;
cp.SurfaceNormal = minusXRotateByShortQZ(p1.Orientation2D);
cp.PenetrationDepth = rsum + .05f;
return true;
}
hd = MathF.Sqrt(hd);
if(hd > rsum)
return false;
d = hd;
hd = 1.0f / hd;
tt.X *= hd;
tt.Y *= hd;
v = Vector3.InverseRotateByShortQZ(tt, p1.Orientation2D);
r1 = 1.0f / MathF.Sqrt(v.X * v.X / p1.AvaSizeXsq + v.Y * v.Y / p1.AvaSizeYsq);
v = Vector3.InverseRotateByShortQZ(tt, p2.Orientation2D);
r2 = 1.0f / MathF.Sqrt(v.X * v.X / p2.AvaSizeXsq + v.Y * v.Y / p2.AvaSizeYsq);
rhsum = r1 + r2 - d;
if (rhsum < 0.5e-3f)
return false;
cp.SurfaceNormal.X = tt.X;
cp.SurfaceNormal.Y = tt.Y;
cp.SurfaceNormal.Z = 0;
cp.Position = p1._position;
cp.Position.Z -= 0.5f * tt.Z;
float hk = r1 - 0.5f * rhsum;
if (hk <= 0)
cp.PenetrationDepth = r1;
else
{
cp.PenetrationDepth = rhsum;
cp.Position.X -= hk * tt.X;
cp.Position.Y -= hk * tt.Y;
}
return true;
}
if (tt.Z > 0)
{
tt.Z -= r;
feet = 1;
}
else
{
tt.Z += r;
feet = -1;
}
d = tt.LengthSquared();
if (d < 1e-6f)
{
cp.Position = p1._position;
cp.SurfaceNormal.X = 0;
cp.SurfaceNormal.Y = 0;
cp.SurfaceNormal.Z = tt.Z > 0 ? 1.0f : -1.0f;
cp.PenetrationDepth = rsum;
return true;
}
d = MathF.Sqrt(d);
float k = rsum - d;
if (k < 0.5e-4f)
return false;
tt *= 1.0f / d;
v = Vector3.InverseRotateByShortQZ(tt, p1.Orientation2D);
r1 = v.X * v.X / p1.AvaSizeXsq + v.Y * v.Y / p1.AvaSizeYsq;
v = Vector3.InverseRotateByShortQZ(tt, p2.Orientation2D);
r2 = v.X * v.X / p2.AvaSizeXsq + v.Y * v.Y / p2.AvaSizeYsq;
if (d > 1.0f / MathF.Sqrt(r1) + 1.0f / MathF.Sqrt(r2))
return false;
cp.SurfaceNormal = tt;
cp.PenetrationDepth = k;
k = p1.CapsuleRadius - 0.5f * k;
tt *= k;
cp.Position = p1._position - tt;
return true;
}
internal ContactPoint SharedChrContact = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void CollideCharChar(OdeCharacter p1, OdeCharacter p2)
{
if (ContactJointCount >= maxContactJoints)
return;
int feetcollision = 0;
if (!CollideSimpleChars(p1, p2, ref SharedChrContact, ref feetcollision))
return;
p1.CollidingObj = true;
p2.CollidingObj = true;
if (feetcollision > 0)
{
p1.CollideNormal = SharedChrContact.SurfaceNormal;
p1.IsColliding = true;
}
else if (feetcollision < 0)
{
p2.CollideNormal = -SharedChrContact.SurfaceNormal;
p2.IsColliding = true;
SharedChrContact.CharacterFeet = true;
}
contactSharedForJoints.surface.mu = 0;
contactSharedForJoints.surface.bounce = 0;
contactSharedForJoints.geom.pos = Unsafe.As<Vector3, UBOdeNative.Vector3>(ref SharedChrContact.Position);
contactSharedForJoints.geom.normal = Unsafe.As<Vector3, UBOdeNative.Vector3>(ref SharedChrContact.SurfaceNormal);
contactSharedForJoints.geom.depth = SharedChrContact.PenetrationDepth;
IntPtr Joint = CreateCharContacJoint();
if (Joint == IntPtr.Zero)
return;
UBOdeNative.JointAttach(Joint, p1.Body, p2.Body);
if (p1.CollisionScore < float.MaxValue)
p1.CollisionScore += 1.0f;
if (p2.CollisionScore < float.MaxValue)
p2.CollisionScore += 1.0f;
bool p1events = p1.SubscribedEvents();
bool p2events = p2.SubscribedEvents();
if (!p2events && !p1events)
return;
Vector3 vel = Vector3.Zero;
if (p2.IsPhysical)
vel = p2.rootVelocity;
if (p1.IsPhysical)
vel -= p1.rootVelocity;
SharedChrContact.RelativeSpeed = Vector3.Dot(vel, SharedChrContact.SurfaceNormal);
if (p2events)
{
p2.AddCollisionEvent(p1.ParentActor.m_baseLocalID, SharedChrContact);
}
if (p1events)
{
SharedChrContact.SurfaceNormal = -SharedChrContact.SurfaceNormal;
SharedChrContact.RelativeSpeed = -SharedChrContact.RelativeSpeed;
SharedChrContact.CharacterFeet = feetcollision > 0;
p1.AddCollisionEvent(p2.ParentActor.m_baseLocalID, SharedChrContact);
}
}
// if mode==1 then use the sphere exit contact, not the entry contact
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool ray_sphere_helper(Vector3 pos, Vector3 dir, float len, Vector3 sphere_pos, float radius,
ref ContactResult cp)
{
Vector3 q = pos - sphere_pos;
float B = q.Dot(dir);
float C = q.LengthSquared() - radius * radius;
// note: if C <= 0 then the start of the ray is inside the sphere
float k = B * B - C;
if (k < 1e-6f)
return false;
k = MathF.Sqrt(k);
float alpha;
if (C >= 0)
{
alpha = -B + k;
if (alpha < 0)
return false;
}
else
{
alpha = -B - k;
if (alpha < 0)
{
alpha = -B + k;
if (alpha < 0)
return false;
}
}
if (alpha > len)
return false;
cp.Pos = pos + dir * alpha;
cp.Normal = sphere_pos - cp.Pos;
cp.Normal.Normalize();
cp.Depth = alpha;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool CollideRaySimpleCapsule(OdeCharacter p1, Vector3 RayStart, Vector3 RayDirection, float RayLength,
ref ContactResult cp)
{
Vector3 cs;
cs.Z = RayStart.Z - p1._position.Z;
float distZ = Math.Abs(cs.Z) - p1.CapsuleSizeZ;
if (distZ >= RayLength)
return false;
cs.X = RayStart.X - p1._position.X;
cs.Y = RayStart.Y - p1._position.Y;
float C = cs.X * cs.X + cs.Y * cs.Y - p1.CapsuleRadius * p1.CapsuleRadius;
if(C > RayLength * RayLength)
return false;
if (C <= 0.5e-4f)
{
if (distZ < 0.5e-4f)
return false;
else
{
cp.Pos.X = p1._position.X;
cp.Pos.Y = p1._position.Y;
cp.Normal.X = 0;
cp.Normal.Y = 0;
cp.Depth = distZ;
if (cs.Z < 0)
{
cp.Pos.Z = p1._position.Z - p1.CapsuleSizeZ;
cp.Normal.Z = -1f;
}
else
{
cp.Pos.Z = p1._position.Z + p1.CapsuleSizeZ;
cp.Normal.Z = 1f;
}
return true;
}
}
float k;
float lz2 = p1.CapsuleSizeZ - p1.CapsuleRadius;
// compute ray collision with infinite cylinder, except for the case where
// the ray is outside the capped cylinder but within the infinite cylinder
// (it that case the ray can only hit endcaps)
float A = RayDirection.X * RayDirection.X + RayDirection.Y * RayDirection.Y;
// A == 0 means that the ray and ccylinder axes are parallel
if (A == 0)
goto _checkends;
float B = 2f * (cs.X * RayDirection.X + cs.Y * RayDirection.Y);
k = B * B - 4f * A * C;
if (k < 1e-6f)
return false;
k = MathF.Sqrt(k);
A = 0.5f / A;
float alpha = (-B - k) * A;
if (alpha < 0)
{
alpha = (-B + k) * A;
if (alpha < 0)
return false;
}
if (alpha > RayLength)
return false;
// the ray intersects the infinite cylinder. check to see if the
// intersection point is between the caps
cp.Pos = RayStart + RayDirection * alpha;
Vector3 q = cp.Pos - p1._position;
if (q.Z >= -lz2 && q.Z <= lz2)
{
cp.Normal.X = q.X;
cp.Normal.Y = q.Y;
cp.Normal.Z = 0;
cp.Normal.Normalize();
cp.Depth = alpha;
return true;
}
// the infinite cylinder intersection point is not between the caps.
// set k to cap position to check.
_checkends:
k = cs.Z < 0 ? -lz2 : lz2;
return ray_sphere_helper(RayStart, RayDirection, RayLength,
new(p1._position.X, p1._position.Y, p1._position.Z + k),
p1.CapsuleRadius, ref cp);
}
}
}