namespace Fusion.Addons.KCC
{
using UnityEngine;
///
/// Main data structure used for movement calculations. Stores data which require rollback, network synchronization + various metadata.
///
public sealed partial class KCCData
{
// PUBLIC MEMBERS
///
/// Frame number, equals to Time.frameCount.
///
public int Frame;
///
/// Tick number, equals to Simulation.Tick or calculated fixed update frame count.
///
public int Tick;
///
/// Relative position of the time between two fixed times. Valid range is <0.0f, 1.0f>
///
public float Alpha;
///
/// Current time, equals to NetworkRunner.SimulationTime or NetworkRunner.SimulationRenderTime or variable if CCD is active.
///
public float Time;
///
/// Partial delta time, variable if CCD is active. Valid range is <0.0f, UpdateDeltaTime>, but can be altered.
///
public float DeltaTime;
///
/// Delta time of full update/tick (CCD independent).
///
/// - FixedUpdate => FixedUpdate
/// - FixedUpdate => Render
/// - Render => Render
///
///
public float UpdateDeltaTime;
///
/// Controls execution of the KCC.
///
public bool IsActive = true;
///
/// Base position, initialized with TargetPosition at the start of each KCC step.
///
public Vector3 BasePosition;
///
/// Desired position before depenetration and post-processing.
///
public Vector3 DesiredPosition;
///
/// Calculated or explicitly set position which is propagated to Transform.
///
public Vector3 TargetPosition;
///
/// Explicitly set look pitch rotation, this should propagate to camera rotation.
///
public float LookPitch
{
get { return _lookPitch; }
set
{
if (_lookPitch != value)
{
_lookPitch = value;
_lookRotationCalculated = false;
_lookDirectionCalculated = false;
}
}
}
///
/// Explicitly set look yaw rotation, this should propagate to camera and transform rotation.
///
public float LookYaw
{
get { return _lookYaw; }
set
{
if (_lookYaw != value)
{
_lookYaw = value;
_lookRotationCalculated = false;
_lookDirectionCalculated = false;
_transformRotationCalculated = false;
_transformDirectionCalculated = false;
}
}
}
///
/// Combination of LookPitch and LookYaw.
///
public Quaternion LookRotation
{
get
{
if (_lookRotationCalculated == false)
{
_lookRotation = Quaternion.Euler(_lookPitch, _lookYaw, 0.0f);
_lookRotationCalculated = true;
}
return _lookRotation;
}
}
///
/// Calculated and cached look direction based on LookRotation.
///
public Vector3 LookDirection
{
get
{
if (_lookDirectionCalculated == false)
{
_lookDirection = LookRotation * Vector3.forward;
_lookDirectionCalculated = true;
}
return _lookDirection;
}
}
///
/// Calculated and cached transform rotation based on Yaw look rotation.
///
public Quaternion TransformRotation
{
get
{
if (_transformRotationCalculated == false)
{
_transformRotation = Quaternion.Euler(0.0f, _lookYaw, 0.0f);
_transformRotationCalculated = true;
}
return _transformRotation;
}
}
///
/// Calculated and cached transform direction based on TransformRotation.
///
public Vector3 TransformDirection
{
get
{
if (_transformDirectionCalculated == false)
{
_transformDirection = TransformRotation * Vector3.forward;
_transformDirectionCalculated = true;
}
return _transformDirection;
}
}
///
/// Non-interpolated world space input direction - based on Keyboard / Joystick / NavMesh / ...
///
public Vector3 InputDirection;
///
/// One-time world space jump impulse based on input.
///
public Vector3 JumpImpulse;
///
/// Gravitational acceleration.
///
public Vector3 Gravity;
///
/// Maximum angle between KCC up direction and ground normal (depenetration vector) in degrees. Valid range is <0, 90>. Default is 75.
///
public float MaxGroundAngle;
///
/// Maximum angle between KCC up direction and wall surface (perpendicular to depenetration vector) in degrees. Valid range is <0, MaxGroundAngle> Default is 5.
///
public float MaxWallAngle;
///
/// Maximum angle between KCC up direction and hang surface (perpendicular to depenetration vector) in degrees. Valid range is <MaxWallAngle, 90> Default is 30.
///
public float MaxHangAngle;
///
/// Single Move/CCD step is split into multiple smaller sub-steps which results in higher overall depenetration quality.
///
public int MaxPenetrationSteps;
///
/// Velocity from external sources, one-time effect - reseted on the end of Move() call to prevent subsequent applications in render, ignoring Mass, example usage - jump pad.
///
public Vector3 ExternalVelocity;
///
/// Acceleration from external sources, continuous effect - value remains same for subsequent applications in render, ignoring Mass, example usage - escalator.
///
public Vector3 ExternalAcceleration;
///
/// Impulse from external sources, one-time effect - reseted on the end of Move() call to prevent subsequent applications in render, affected by Mass, example usage - explosion.
///
public Vector3 ExternalImpulse;
///
/// Force from external sources, continuous effect - value remains same for subsequent applications in render, affected by Mass, example usage - attractor.
///
public Vector3 ExternalForce;
///
/// Absolute position delta which is consumed by single move. It can also be set from ProcessPhysicsQuery and still consumed by currently executed move (useful for depenetration corrections).
///
public Vector3 ExternalDelta;
///
/// Speed used to calculate KinematicVelocity.
///
public float KinematicSpeed;
///
/// Calculated kinematic tangent, based on KinematicDirection and affected by other factors like ground normal, used to calculate KinematicVelocity.
///
public Vector3 KinematicTangent;
///
/// Desired kinematic direction, based on InputDirection and other factors, used to calculate KinematicVelocity.
///
public Vector3 KinematicDirection;
///
/// Velocity calculated from InputDirection, KinematicDirection, KinematicTangent and KinematicSpeed.
///
public Vector3 KinematicVelocity;
///
/// Velocity calculated from Gravity, ExternalVelocity, ExternalAcceleration, ExternalImpulse, ExternalForce and JumpImpulse.
///
public Vector3 DynamicVelocity;
///
/// Final calculated velocity used for position change, combined KinematicVelocity and DynamicVelocity.
///
public Vector3 DesiredVelocity => KinematicVelocity + DynamicVelocity;
///
/// Speed calculated from real position change.
///
public float RealSpeed;
///
/// Velocity calculated from real position change.
///
public Vector3 RealVelocity;
///
/// Counter in how many frames the JumpImpulse was applied in a row.
/// Fixed update have 1 at max. In render update value higher than 1 indicates that jump happened in earlier render frame and should be processed next fixed update as well.
///
public int JumpFrames;
///
/// Flag that indicates KCC has jumped in current tick/frame.
///
public bool HasJumped => JumpFrames == 1;
///
/// Flag that indicates KCC has teleported in current tick.
///
public bool HasTeleported;
///
/// Flag that indicates KCC is touching a collider with normal angle lower than MaxGroundAngle.
///
public bool IsGrounded;
///
/// Same as IsGrounded previous tick or physics query.
///
public bool WasGrounded;
///
/// Indicates the KCC temporarily or permanently lost grounded state.
///
public bool IsOnEdge => IsGrounded == false && WasGrounded == true;
///
/// Indicates the KCC is stepping up.
///
public bool IsSteppingUp;
///
/// Same as IsSteppingUp previous tick or physics query.
///
public bool WasSteppingUp;
///
/// Indicates the KCC temporarily lost grounded state and is snapping to ground.
///
public bool IsSnappingToGround;
///
/// Same as IsSnappingToGround previous tick or physics query.
///
public bool WasSnappingToGround;
///
/// Combined normal of all touching colliders. Normals less distant from up direction have bigger impacton final normal.
///
public Vector3 GroundNormal;
///
/// Tangent to GroundNormal, can be calculated from DesiredVelocity or TargetRotation if GroundNormal and up direction is same.
///
public Vector3 GroundTangent;
///
/// Position of the KCC collider surface touching the ground collider.
///
public Vector3 GroundPosition;
///
/// Distance from ground.
///
public float GroundDistance;
///
/// Difference between ground normal and up direction in degrees.
///
public float GroundAngle;
///
/// Collection of networked collisions. Represents colliders the KCC interacts with.
/// Only objects with NetworkObject component are stored for compatibility with local prediction.
///
public readonly KCCCollisions Collisions = new KCCCollisions();
///
/// Collection of manually registered modifiers (for example processors) the KCC interacts with.
/// Only objects with NetworkObject component are stored for compatibility with local prediction.
///
public readonly KCCModifiers Modifiers = new KCCModifiers();
///
/// Collection of ignored colliders.
/// Only objects with NetworkObject component are stored for compatibility with local prediction.
///
public readonly KCCIgnores Ignores = new KCCIgnores();
///
/// Collection of colliders/triggers the KCC overlaps (radius + extent).
/// This collection is not synchronized over the network! NetworkObject component is not needed, only local history is supported.
///
public readonly KCCHits Hits = new KCCHits();
// PRIVATE MEMBERS
private float _lookPitch;
private float _lookYaw;
private Quaternion _lookRotation;
private bool _lookRotationCalculated;
private Vector3 _lookDirection;
private bool _lookDirectionCalculated;
private Quaternion _transformRotation;
private bool _transformRotationCalculated;
private Vector3 _transformDirection;
private bool _transformDirectionCalculated;
// PUBLIC METHODS
///
/// Returns the look rotation vector with selected axes.
///
public Vector2 GetLookRotation(bool pitch = true, bool yaw = true)
{
Vector2 lookRotation = default;
if (pitch == true) { lookRotation.x = _lookPitch; }
if (yaw == true) { lookRotation.y = _lookYaw; }
return lookRotation;
}
///
/// Add pitch and yaw look rotation. Resulting values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void AddLookRotation(float pitchDelta, float yawDelta)
{
if (pitchDelta != 0.0f)
{
LookPitch = Mathf.Clamp(LookPitch + pitchDelta, -90.0f, 90.0f);
}
if (yawDelta != 0.0f)
{
float lookYaw = LookYaw + yawDelta;
while (lookYaw > 180.0f)
{
lookYaw -= 360.0f;
}
while (lookYaw < -180.0f)
{
lookYaw += 360.0f;
}
LookYaw = lookYaw;
}
}
///
/// Add pitch and yaw look rotation. Resulting values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void AddLookRotation(float pitchDelta, float yawDelta, float minPitch, float maxPitch)
{
if (pitchDelta != 0.0f)
{
if (minPitch < -90.0f) { minPitch = -90.0f; }
if (maxPitch > 90.0f) { maxPitch = 90.0f; }
if (maxPitch < minPitch) { maxPitch = minPitch; }
LookPitch = Mathf.Clamp(LookPitch + pitchDelta, minPitch, maxPitch);
}
if (yawDelta != 0.0f)
{
float lookYaw = LookYaw + yawDelta;
while (lookYaw > 180.0f)
{
lookYaw -= 360.0f;
}
while (lookYaw < -180.0f)
{
lookYaw += 360.0f;
}
LookYaw = lookYaw;
}
}
///
/// Add pitch (x) and yaw (y) look rotation. Resulting values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void AddLookRotation(Vector2 lookRotationDelta)
{
AddLookRotation(lookRotationDelta.x, lookRotationDelta.y);
}
///
/// Add pitch (x) and yaw (y) look rotation. Resulting values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void AddLookRotation(Vector2 lookRotationDelta, float minPitch, float maxPitch)
{
AddLookRotation(lookRotationDelta.x, lookRotationDelta.y, minPitch, maxPitch);
}
///
/// Set pitch and yaw look rotation. Values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void SetLookRotation(float pitch, float yaw)
{
KCCUtility.ClampLookRotationAngles(ref pitch, ref yaw);
LookPitch = pitch;
LookYaw = yaw;
}
///
/// Set pitch and yaw look rotation. Values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void SetLookRotation(float pitch, float yaw, float minPitch, float maxPitch)
{
KCCUtility.ClampLookRotationAngles(ref pitch, ref yaw, minPitch, maxPitch);
LookPitch = pitch;
LookYaw = yaw;
}
///
/// Set pitch (x) and yaw (y) look rotation. Values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void SetLookRotation(Vector2 lookRotation)
{
SetLookRotation(lookRotation.x, lookRotation.y);
}
///
/// Set pitch (x) and yaw (y) look rotation. Values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void SetLookRotation(Vector2 lookRotation, float minPitch, float maxPitch)
{
SetLookRotation(lookRotation.x, lookRotation.y, minPitch, maxPitch);
}
///
/// Set pitch and yaw look rotation. Roll is ignored (not supported). Values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw).
/// Changes are not propagated to Transform component.
///
public void SetLookRotation(Quaternion lookRotation, bool preservePitch = false, bool preserveYaw = false)
{
KCCUtility.GetClampedLookRotationAngles(lookRotation, out float pitch, out float yaw);
if (preservePitch == false) { LookPitch = pitch; }
if (preserveYaw == false) { LookYaw = yaw; }
}
///
/// Clear all properties which should not propagate to next tick/frame if IsActive == false.
///
public void ClearTransientProperties()
{
JumpFrames = default;
JumpImpulse = default;
ExternalVelocity = default;
ExternalAcceleration = default;
ExternalImpulse = default;
ExternalForce = default;
ClearUserTransientProperties();
}
public void Clear()
{
ClearUserData();
Collisions.Clear();
Modifiers.Clear();
Ignores.Clear();
Hits.Clear();
}
public void CopyFromOther(KCCData other)
{
Frame = other.Frame;
Tick = other.Tick;
Alpha = other.Alpha;
Time = other.Time;
DeltaTime = other.DeltaTime;
UpdateDeltaTime = other.UpdateDeltaTime;
IsActive = other.IsActive;
BasePosition = other.BasePosition;
DesiredPosition = other.DesiredPosition;
TargetPosition = other.TargetPosition;
LookPitch = other.LookPitch;
LookYaw = other.LookYaw;
InputDirection = other.InputDirection;
JumpImpulse = other.JumpImpulse;
Gravity = other.Gravity;
MaxGroundAngle = other.MaxGroundAngle;
MaxWallAngle = other.MaxWallAngle;
MaxHangAngle = other.MaxHangAngle;
MaxPenetrationSteps = other.MaxPenetrationSteps;
ExternalVelocity = other.ExternalVelocity;
ExternalAcceleration = other.ExternalAcceleration;
ExternalImpulse = other.ExternalImpulse;
ExternalForce = other.ExternalForce;
ExternalDelta = other.ExternalDelta;
KinematicSpeed = other.KinematicSpeed;
KinematicTangent = other.KinematicTangent;
KinematicDirection = other.KinematicDirection;
KinematicVelocity = other.KinematicVelocity;
DynamicVelocity = other.DynamicVelocity;
RealSpeed = other.RealSpeed;
RealVelocity = other.RealVelocity;
JumpFrames = other.JumpFrames;
HasTeleported = other.HasTeleported;
IsGrounded = other.IsGrounded;
WasGrounded = other.WasGrounded;
IsSteppingUp = other.IsSteppingUp;
WasSteppingUp = other.WasSteppingUp;
IsSnappingToGround = other.IsSnappingToGround;
WasSnappingToGround = other.WasSnappingToGround;
GroundNormal = other.GroundNormal;
GroundTangent = other.GroundTangent;
GroundPosition = other.GroundPosition;
GroundDistance = other.GroundDistance;
GroundAngle = other.GroundAngle;
Collisions.CopyFromOther(other.Collisions);
Modifiers.CopyFromOther(other.Modifiers);
Ignores.CopyFromOther(other.Ignores);
Hits.CopyFromOther(other.Hits);
CopyUserDataFromOther(other);
}
// PARTIAL METHODS
partial void ClearUserTransientProperties();
partial void ClearUserData();
partial void CopyUserDataFromOther(KCCData other);
}
}