using System;
using System.Numerics;
using ACE.Server.Physics.Animation;
using ACE.Server.Physics.Collision;
using ACE.Server.Physics.Common;
using ACE.Server.Physics.Extensions;
namespace ACE.Server.Physics
{
///
/// A cylinder sphere
///
public class CylSphere: IEquatable
{
///
/// The base of the cylinder sphere
///
public Vector3 LowPoint;
///
/// The height of the cylinder sphere
///
public float Height;
///
/// The radius of the cylinder sphere
///
public float Radius;
///
/// Default constructor
///
public CylSphere()
{
LowPoint = Vector3.Zero;
}
///
/// Constructs a cylinder sphere from components
///
/// The base of the cylinder sphere
/// The height of the object
/// The object radius
public CylSphere(Vector3 lowPoint, float height, float radius)
{
LowPoint = lowPoint;
Height = height;
Radius = radius;
}
///
/// Constructs a cylsphere loaded from portal.dat
///
public CylSphere(DatLoader.Entity.CylSphere cylSphere)
{
LowPoint = cylSphere.Origin;
Height = cylSphere.Height;
Radius = cylSphere.Radius;
}
///
/// Constructs a cylinder sphere from components and scale
///
/// The base of the cylinder sphere
/// The height of the object
/// The object radius
/// The scale to be applied to all components
public CylSphere(Vector3 lowPoint, float height, float radius, float scale)
{
LowPoint = lowPoint * scale;
Height = height * scale;
Radius = radius * scale;
}
///
/// Redirects a cylinder sphere to be on collision course towards a point
///
/// The transition information for the sphere
/// The spherical point to redirect towards
/// Used for calculating the collision normal
/// The sum of the sphere and spherical point radii
/// Used as an offset in path.GlobalCurrCenter to determine movement
/// The TransitionState either collided or adjusted
public TransitionState CollideWithPoint(Transition transition, Sphere checkPos, Vector3 disp, float radsum, int sphereNum)
{
var obj = transition.ObjectInfo;
var path = transition.SpherePath;
var collisions = transition.CollisionInfo;
Vector3 collisionNormal;
var definate = CollisionNormal(transition, checkPos, disp, radsum, sphereNum, out collisionNormal);
if (Vec.NormalizeCheckSmall(ref collisionNormal))
return TransitionState.Collided;
if (!obj.State.HasFlag(ObjectInfoState.PerfectClip))
{
collisions.SetCollisionNormal(collisionNormal);
return TransitionState.Collided;
}
var globCenter = path.GlobalCurrCenter[0].Center;
var movement = LandDefs.GetBlockOffset(path.CurPos.ObjCellID, path.CheckPos.ObjCellID);
movement += checkPos.Center - globCenter;
var old_disp = globCenter - LowPoint;
radsum += PhysicsGlobals.EPSILON;
// this is similar to ray/sphere intersection, could be inlined...
var xyMoveLenSq = movement.LengthSquared2D();
var xyDiff = -movement.Dot2D(old_disp);
var diffSq = xyDiff * xyDiff - (old_disp.LengthSquared2D() - radsum * radsum) * xyMoveLenSq;
var diff = (float)Math.Sqrt(diffSq);
Vector3 scaledMovement, offset;
float time; // calculated below, refactor
if (!definate)
{
if (Math.Abs(movement.Z) < PhysicsGlobals.EPSILON)
return TransitionState.Collided;
if (movement.Z > 0.0f)
{
collisionNormal = new Vector3(0, 0, -1.0f);
time = (movement.Z + checkPos.Radius) / movement.Z * -1.0f;
}
else
{
collisionNormal = new Vector3(0, 0, 1.0f);
time = (checkPos.Radius + Height - movement.Z) / movement.Z;
}
scaledMovement = movement * time;
var scaledLenSq = (scaledMovement + old_disp).LengthSquared2D();
if (scaledLenSq >= radsum * radsum)
{
if (Math.Abs(xyMoveLenSq) < PhysicsGlobals.EPSILON)
return TransitionState.Collided;
if (diffSq >= 0.0f && xyMoveLenSq > PhysicsGlobals.EPSILON)
{
if (xyDiff - diff < 0.0f)
time = (float)((diff - movement.Dot2D(old_disp)) / xyMoveLenSq);
else
time = (float)((xyDiff - diff) / xyMoveLenSq);
scaledMovement = movement * time;
}
collisionNormal = (scaledMovement + globCenter - LowPoint) / radsum;
collisionNormal.Z = 0.0f;
}
if (time < 0.0f || time > 1.0f)
return TransitionState.Collided;
offset = globCenter - scaledMovement - checkPos.Center;
path.AddOffsetToCheckPos(offset, checkPos.Radius);
collisions.SetCollisionNormal(collisionNormal);
return TransitionState.Adjusted;
}
if (collisionNormal.Z != 0.0f)
{
if (Math.Abs(movement.Z) < PhysicsGlobals.EPSILON)
return TransitionState.Collided;
if (movement.Z > 0.0f)
time = -((old_disp.Z + checkPos.Radius) / movement.Z);
else
time = (checkPos.Radius + Height - old_disp.Z) / movement.Z;
scaledMovement = movement * time;
if (time < 0.0f || time > 1.0f)
return TransitionState.Collided;
offset = globCenter + scaledMovement - checkPos.Center;
path.AddOffsetToCheckPos(offset, checkPos.Radius);
collisions.SetCollisionNormal(collisionNormal);
return TransitionState.Adjusted;
}
// duplicated from above, refactor...
if (diffSq < 0.0f || xyMoveLenSq < PhysicsGlobals.EPSILON)
return TransitionState.Collided;
if (xyDiff - diff < 0.0f)
time = (float)((diff - movement.Dot2D(old_disp)) / xyMoveLenSq);
else
time = (float)((xyDiff - diff) / xyMoveLenSq);
scaledMovement = movement * time;
if (time < 0.0f || time > 1.0f)
return TransitionState.Collided;
collisionNormal = (scaledMovement + globCenter - LowPoint) / radsum;
collisionNormal.Z = 0.0f;
offset = globCenter + scaledMovement - checkPos.Center;
path.AddOffsetToCheckPos(offset, checkPos.Radius);
collisions.SetCollisionNormal(collisionNormal);
return TransitionState.Adjusted;
}
///
/// Returns true if this CylSphere collides with a sphere
///
public bool CollidesWithSphere(Sphere checkPos, Vector3 disp, float radsum)
{
var result = false;
if (disp.X * disp.X + disp.Y * disp.Y <= radsum * radsum)
{
if (checkPos.Radius - PhysicsGlobals.EPSILON + Height * 0.5f >= Math.Abs(Height * 0.5f - disp.Z))
result = true;
}
return result;
}
///
/// Determines if this CylSphere collides with anything during its transition
///
/// The transition path for this cylinder sphere
/// The collision result for this transition path
public TransitionState IntersectsSphere(Transition transition)
{
var obj = transition.ObjectInfo;
var path = transition.SpherePath;
var globSphere = path.GlobalSphere[0];
var disp = globSphere.Center - LowPoint;
Sphere globSphere_ = null;
Vector3 disp_ = Vector3.Zero;
if (path.NumSphere > 1)
{
globSphere_ = path.GlobalSphere[1];
disp_ = globSphere_.Center - LowPoint;
}
var radsum = Radius - PhysicsGlobals.EPSILON + globSphere.Radius;
if (path.InsertType == InsertType.Placement || path.ObstructionEthereal)
{
if (CollidesWithSphere(globSphere, disp, radsum))
return TransitionState.Collided;
if (path.NumSphere > 1 && CollidesWithSphere(globSphere_, disp_, radsum))
return TransitionState.Collided;
return TransitionState.OK;
}
else
{
if (path.StepDown)
return StepSphereDown(transition, globSphere, disp, radsum);
if (path.CheckWalkable)
{
if (CollidesWithSphere(globSphere, disp, radsum))
return TransitionState.Collided;
if (path.NumSphere > 1)
{
if (CollidesWithSphere(globSphere_, disp_, radsum))
return TransitionState.Collided;
}
return TransitionState.OK;
}
if (!path.Collide)
{
if (obj.State.HasFlag(ObjectInfoState.Contact) || obj.State.HasFlag(ObjectInfoState.OnWalkable))
{
if (CollidesWithSphere(globSphere, disp, radsum))
return StepSphereUp(transition, globSphere, disp, radsum);
if (path.NumSphere > 1)
{
if (CollidesWithSphere(globSphere_, disp_, radsum))
return SlideSphere(transition, globSphere_, disp, radsum, 1);
}
}
else if (obj.State.HasFlag(ObjectInfoState.PathClipped))
{
if (CollidesWithSphere(globSphere, disp, radsum))
return CollideWithPoint(transition, globSphere, disp, radsum, 0);
}
else
{
if (CollidesWithSphere(globSphere, disp, radsum))
return LandOnCylinder(transition, globSphere, disp, radsum);
if (path.NumSphere > 1)
{
if (CollidesWithSphere(globSphere_, disp_, radsum))
return CollideWithPoint(transition, globSphere_, disp_, radsum, 1);
}
}
return TransitionState.OK;
}
if (CollidesWithSphere(globSphere, disp, radsum) || path.NumSphere > 1 && CollidesWithSphere(globSphere_, disp_, radsum))
{
var blockOffset = path.GetCurPosCheckPosBlockOffset();
var movement = path.GlobalCurrCenter[0].Center - globSphere.Center - blockOffset;
if (Math.Abs(movement.Z) < PhysicsGlobals.EPSILON)
return TransitionState.Collided;
var timecheck = (Height + globSphere.Radius - disp.Z) / movement.Z;
var offset = movement * timecheck;
var offsetDispSum = offset + disp;
if (radsum * radsum < offsetDispSum.Dot2D(offsetDispSum))
return TransitionState.OK;
var t = (1.0f - timecheck) * transition.SpherePath.WalkInterp;
if (t >= transition.SpherePath.WalkInterp || t < -0.1f)
return TransitionState.Collided;
var pDist = globSphere.Center + offset;
pDist.Z -= globSphere.Radius;
var contactPlane = new Plane(Vector3.UnitZ, -Vector3.Dot(Vector3.UnitZ, pDist));
transition.CollisionInfo.SetContactPlane(contactPlane, true);
transition.CollisionInfo.ContactPlaneCellID = transition.SpherePath.CheckPos.ObjCellID;
transition.SpherePath.WalkInterp = t;
transition.SpherePath.AddOffsetToCheckPos(offset, globSphere.Radius);
return TransitionState.Adjusted;
}
}
return TransitionState.OK;
}
///
/// Determines if this sphere collides with anything during its transition
///
/// The collision result for this transition path
public TransitionState IntersectsSphere(Position pos, float scale, Transition transition)
{
var path = transition.SpherePath;
path.CacheLocalSpaceSphere(pos, 1.0f);
var global_cylsphere = new CylSphere(LowPoint, Height, Radius, scale);
global_cylsphere.LowPoint = path.CheckPos.LocalToGlobal(pos, global_cylsphere.LowPoint);
return global_cylsphere.IntersectsSphere(transition);
}
///
/// Handles the collision when an object lands on a cylinder sphere
///
public TransitionState LandOnCylinder(Transition transition, Sphere checkPos, Vector3 disp, float radsum)
{
Vector3 collisionNormal;
CollisionNormal(transition, checkPos, disp, radsum, 0, out collisionNormal);
if (Vec.NormalizeCheckSmall(ref collisionNormal))
return TransitionState.Collided;
var path = transition.SpherePath;
path.SetCollide(collisionNormal);
path.WalkableAllowance = PhysicsGlobals.LandingZ;
return TransitionState.Adjusted;
}
///
/// Attempts to slide the CylSphere from a collision
///
public TransitionState SlideSphere(Transition transition, Sphere checkPos, Vector3 disp, float radsum, int sphereNum)
{
Vector3 collisionNormal;
CollisionNormal(transition, checkPos, disp, radsum, sphereNum, out collisionNormal);
if (Vec.NormalizeCheckSmall(ref collisionNormal))
return TransitionState.Collided;
return checkPos.SlideSphere(transition, ref collisionNormal, transition.SpherePath.GlobalCurrCenter[sphereNum].Center);
}
///
/// Attempts to move the CylSphere down from a collision
///
public TransitionState StepSphereDown(Transition transition, Sphere checkPos, Vector3 disp, float radsum)
{
var path = transition.SpherePath;
Sphere globSphere_ = null;
Vector3 disp_ = Vector3.Zero;
if (path.NumSphere > 1)
{
globSphere_ = path.GlobalSphere[1];
disp_ = globSphere_.Center - LowPoint;
}
if (CollidesWithSphere(checkPos, disp, radsum) || path.NumSphere > 1 && CollidesWithSphere(globSphere_, disp_, radsum))
{
var stepScale = path.StepDownAmt * path.WalkInterp;
if (Math.Abs(stepScale) < PhysicsGlobals.EPSILON)
return TransitionState.Collided;
var deltaz = Height + checkPos.Radius - disp.Z;
var interp = (1.0f - deltaz / stepScale) * path.WalkInterp;
if (interp >= path.WalkInterp || interp < -0.1f)
return TransitionState.Collided;
var normal = Vector3.UnitZ;
var contactPoint = new Vector3(checkPos.Center.X, checkPos.Center.Y, checkPos.Center.Z + (deltaz - checkPos.Radius));
var contactPlane = new Plane(normal, -Vector3.Dot(normal, contactPoint));
var collisions = transition.CollisionInfo;
collisions.SetContactPlane(contactPlane, true); // is water?
path.WalkInterp = interp;
path.AddOffsetToCheckPos(new Vector3(0, 0, deltaz), checkPos.Radius);
return TransitionState.Adjusted;
}
return TransitionState.OK;
}
///
/// Attempts to move the CylSphere up from a collision
///
public TransitionState StepSphereUp(Transition transition, Sphere checkPos, Vector3 disp, float radsum)
{
var obj = transition.ObjectInfo;
if (obj.StepUpHeight < checkPos.Radius + Height - disp.Z)
return SlideSphere(transition, checkPos, disp, radsum, 0);
Vector3 collisionNormal;
CollisionNormal(transition, checkPos, disp, radsum, 0, out collisionNormal);
if (Vec.NormalizeCheckSmall(ref collisionNormal))
return TransitionState.Collided;
var path = transition.SpherePath;
var globalPos = path.LocalSpacePos.LocalToGlobalVec(collisionNormal);
if (transition.StepUp(globalPos))
return TransitionState.OK;
else
return path.StepUpSlide(transition);
}
///
/// Returns the collision normal for this CylSphere transition
///
public bool CollisionNormal(Transition transition, Sphere checkPos, Vector3 _disp, float radsum, int sphereNum, out Vector3 normal)
{
var disp = transition.SpherePath.GlobalCurrCenter[sphereNum].Center - LowPoint;
if (radsum * radsum < disp.LengthSquared2D())
{
normal = new Vector3(disp.X, disp.Y, 0);
return checkPos.Radius - PhysicsGlobals.EPSILON + Height * 0.5f >= Math.Abs(Height * 0.5f - disp.Z)
|| Math.Abs(disp.Z - _disp.Z) <= PhysicsGlobals.EPSILON;
}
var normZ = (_disp.Z - disp.Z <= 0.0f) ? 1 : -1;
normal = new Vector3(0, 0, normZ);
return true;
}
public bool Equals(CylSphere cylSphere)
{
return cylSphere != null && Height == cylSphere.Height && Radius == cylSphere.Radius && LowPoint == cylSphere.LowPoint;
}
public override int GetHashCode()
{
int hash = 0;
hash = (hash * 397) ^ Height.GetHashCode();
hash = (hash * 397) ^ Radius.GetHashCode();
hash = (hash * 397) ^ LowPoint.GetHashCode();
return hash;
}
}
}