//---------------------------------------------------------------------------------------------------------------------------------------------------
//
// Copyright (C)2007 DarkWynter Studios. All rights reserved.
//
//---------------------------------------------------------------------------------------------------------------------------------------------------
// {Contact : darkwynter.com for licensing information
//---------------------------------------------------------------------------------------------------------------------------------------------------
#define HACK_MOVEMENT
namespace DarkWynter.Engine.Physics
{
#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Globals;
using GameObjects;
using Utilities; // Logging...
#endregion
///
/// Mass object associated with all Game Objects, which encapsulates physical properties into an invisible object.
/// Mass processes it's own Updates, though Collision.cs is used to handle mass-to-mass interactions.
/// Uses a right-handed coordinate system.
///
public class Mass
{
///
/// Pointer from this mass back up to the game object associated with it
///
public GameObject gameObjectPointer;
// Position
///
/// The objects center location
///
public Vector3 currentPosition = new Vector3();
///
/// Needed for updating the lookup map
///
public Vector3 lastPosition = new Vector3();
///
/// Stores current rotation
///
public Quaternion currentRotation = new Quaternion();
///
/// Straight ahead - normalized
///
public Vector3 normalVector = new Vector3();
///
/// Straight up - normalized
///
public Vector3 upVector = new Vector3();
///
/// Straight to the right - normalized
///
public Vector3 perpVector = new Vector3();
// Motion
///
/// The object's current acceleration
///
public Vector3 acceleration = new Vector3();
///
/// Un-Normalized Vector in the direction of object movement
///
public Vector3 velocity = new Vector3();
///
/// Used to sum the forces on a GameObject during Update
///
public Vector3 totalForce = new Vector3();
// Spatial Volume
///
/// Bounding volume of this mass
///
public BoundingVolume boundingVolume = new BoundingVolume();
///
/// Model Scale Factor
///
public Vector3 scale = new Vector3(1.0f);
///
/// Object height above the terrain. Based on model properties
///
public float objectHeight;
// Energy
///
/// Initial Fire energy given to player
///
public float energy = 0.0f;
#region Attributes
///
/// Tells us what type of an object his is
///
public Enums_Engine.ObjectType objectType = new Enums_Engine.ObjectType();
///
/// Temperature change check
///
public bool isChanging = false;
///
/// Motion check
///
public bool isMoving = false;
///
/// Have we completed all collisions on this mass
///
public bool collisionChecked = false;
///
/// Does the object need to be recycled
///
public bool deadObject = false;
// Properties
///
/// Kilos
///
public float mass = 0.0f;
///
/// Used to calculate bonding factor to other game objects
///
public float adhesion = 0.1f;
///
/// Used to calculate bonding factor to like objects
///
public float cohesion = 0.5f;
///
/// Temperature (Celcius) at which a particle changes state
///
public float stateChangeThreshold = 0;
///
/// Coefficient Of Restitution for this object
///
public float COR;
///
/// Static Friction Coefficient of this object
///
public float staticFrictionCoefficient;
///
/// Dynamic Friction Coefficient of this object
///
public float dynamicFrictionCoefficient;
///
/// Amount of damage to be applied when this mass falls/hits the terrain
///
public float fallingDamageMultiplier = 0.0f;
///
/// Motion type of this object
///
public Enums_Engine.MovementType movementType = Enums_Engine.MovementType.HOVER;
///
/// Change in energy value
///
private float newEnergy = 0.0f;
///
/// Orientation of the Sensor
///
public Vector3 sensorDirection = new Vector3();
///
/// Used for falling damage calculations
///
public float lastMaxHeight = 0;
///
/// Number of times this object has collided with something
///
public int numberOfCollision = 0;
#endregion
///
/// Constructor
///
public Mass()
{
totalForce = Vector3.Zero;
velocity = Vector3.Zero;
}
///
/// Reset mass to initial state
///
public void ClearValues()
{
// Reset everything here
// Called by constructor...
objectType = Enums_Engine.ObjectType.NONE;
objectHeight = 0;
gameObjectPointer = null;
// Orientation
currentRotation = Quaternion.Identity; // Stores current rotation information
normalVector = Vector3.Zero; // Straight ahead - normalized
upVector = Vector3.Zero; // Straight up - normalized
perpVector = Vector3.Zero; // Straight to the right?(left?)
numberOfCollision = 0;
// Position
currentPosition = Vector3.Zero; // The objects center location
lastPosition = Vector3.Zero; // Needed for updating the lookup map
// Motion Variables
acceleration = Vector3.Zero;
velocity = Vector3.Zero; // Un-Normalized Vector in the direction of object movement
totalForce = Vector3.Zero; // Used to sum the forces on a GameObject during Update
// Reaction Properties
mass = 1; // Kilos
scale = new Vector3(1.0f); // Model Scale Factor
adhesion = 0; // Used to calculate bonding factor to other game objects
cohesion = 0; // Used to calculate bonding factor to like objects
// Energy State
energy = 0.0f; // Initial Fire energy given to player
newEnergy = 0.0f;
stateChangeThreshold = 0; // Temperature (Celcius) at which a particle changes state
COR = 0.0f; // Coefficient Of Restitution for this object
staticFrictionCoefficient = 0.0f; // Static Friction Coefficient of this object
dynamicFrictionCoefficient = 0.0f; // Dynamic Friction Coefficient of this object
isChanging = false;
isMoving = false;
collisionChecked = false;
fallingDamageMultiplier = 0.0f;
deadObject = false;
}
///
/// Update the mass's position
///
public void Update()
{
// Signal collision that this object needs collision detection
collisionChecked = false;
// If mass is moving, update physics
// .. else, set isMoving false to indicate no update needed
if (totalForce != Vector3.Zero || velocity != Vector3.Zero)
{
isMoving = true;
// Set last position to the old current position
lastPosition = currentPosition;
// Add force if below speed limit and motion type is Hover
if (totalForce.Length() > Statics_Engine.PlayerSettings.MAX_HOVER_SPEED && movementType == Enums_Engine.MovementType.HOVER)
{
totalForce.Normalize();
totalForce *= Statics_Engine.PlayerSettings.MAX_HOVER_SPEED;
}
// Acceleration calc
acceleration = totalForce / mass;
if (float.IsNaN(acceleration.X) || float.IsNaN(acceleration.Y) || float.IsNaN(acceleration.Z))
{
System.Diagnostics.Debug.WriteLine("acceleration = NaN");
Logging.addNan();
}
// Force has been applied.. zero it out..
totalForce = Vector3.Zero;
// Movement according to time S = u*t + 1/2*g*t^2
currentPosition = lastPosition + velocity * Statics_Engine.SystemSettings.dt + (0.5f * acceleration * Statics_Engine.SystemSettings.dt * Statics_Engine.SystemSettings.dt);
if (float.IsNaN(currentPosition.X))
{
System.Diagnostics.Debug.WriteLine("CurrentPosition = NaN");
Logging.addNan();
currentPosition = lastPosition;
}
// Calculate velocity
if (movementType == Enums_Engine.MovementType.WALK && gameObjectPointer.heightDifference >= 0.0f)
{
velocity = Vector3.Zero;
}
else
{
velocity += acceleration * Statics_Engine.SystemSettings.dt;
}
// CLEAN: ?? set the height
if (currentPosition.Y > lastMaxHeight)
{
lastMaxHeight = currentPosition.Y;
}
// Ensure that mass is within level boundaries
EnforceLevelBoundary();
}
else
{
isMoving = false;
}
// Check if new energy has been added to the object
if (newEnergy != 0.0f)
{
// Call the energy update function
UpdateEnergy();
}
}
///
/// Boundary checking
///
public void EnforceLevelBoundary()
{
// X = 0 plane
if (currentPosition.X < 0.0f)
{
// Clamp the position
currentPosition.X = 1.0f;
lastPosition.X = 0.0f;
AddForce(new Vector3(-2.0f * mass * velocity.X * COR / Statics_Engine.SystemSettings.dt, 0.0f, 0.0f));
}
// X = mapSize plane
if (currentPosition.X > (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor)
{
// Clamp the position
currentPosition.X = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 1;
lastPosition.X = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 2;
AddForce(new Vector3(-2.0f * mass * velocity.X * COR / Statics_Engine.SystemSettings.dt, 0.0f, 0.0f));
}
//// Y = 0 plane
//if (currentPosition.Y < 0.0f)
//{
// // Clamp the position
// currentPosition.Y = 1.0f;
// lastPosition.Y = 0.0f;
// AddForce(new Vector3(0.0f, -2.0f * mass * velocity.Y * COR / Statics.SystemSettings.dt, 0.0f));
//}
// Y = mapSize plane
if (currentPosition.Y > (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor)
{
// Clamp the position
currentPosition.Y = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 1;
lastPosition.Y = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 2;
AddForce(new Vector3(0.0f, -2.0f * mass * velocity.Y * COR / Statics_Engine.SystemSettings.dt, 0.0f));
}
// Z = 0 plane
if (currentPosition.Z < 0.0f)
{
// Clamp the position
currentPosition.Z = 1.0f;
lastPosition.Z = 0.0f;
AddForce(new Vector3(0.0f, 0.0f, -2.0f * mass * velocity.Z * COR / Statics_Engine.SystemSettings.dt));
}
// Z = mapSize plane
if (currentPosition.Z > (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor)
{
// Clamp the position
currentPosition.Z = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 1;
lastPosition.Z = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 2;
AddForce(new Vector3(0.0f, 0.0f, -2.0f * mass * velocity.Z * COR / Statics_Engine.SystemSettings.dt));
}
if (float.IsNaN(currentPosition.X))
{
Logging.addNan();
currentPosition.X = 1.0f;
lastPosition.X = 1.0f;
}
if (float.IsNaN(currentPosition.Y))
{
Logging.addNan();
currentPosition.Y = 1.0f;
lastPosition.Y = 0.0f;
}
if (float.IsNaN(currentPosition.Z))
{
Logging.addNan();
currentPosition.Z = 1.0f;
lastPosition.Z = 0.0f;
}
/////////////////////////////////////////
// this is a hack to fix the crashes due to lastPosition being invalid.. we really shouldn't have to do this
//
// X = 0 plane
if (lastPosition.X < 0.0f)
{
// Clamp the position
lastPosition.X = 0.0f;
}
// X = mapSize plane
if (lastPosition.X > (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor)
{
// Clamp the position
lastPosition.X = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 2;
}
// Y = 0 plane
if (lastPosition.Y < 0.0f)
{
// Clamp the position
lastPosition.Y = 0.0f;
}
// Y = mapSize plane
if (lastPosition.Y > (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor)
{
// Clamp the position
lastPosition.Y = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 2;
}
// Z = 0 plane
if (lastPosition.Z < 0.0f)
{
// Clamp the position
lastPosition.Z = 0.0f;
}
// Z = mapSize plane
if (lastPosition.Z > (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor)
{
// Clamp the position
lastPosition.Z = (Statics_Engine.TerrainSettings.vertexMapSize - 1) * Statics_Engine.TerrainSettings.terrainScaleFactor - 2;
}
}
///
/// Set this mass's position vectors
///
/// Center position
/// Reference position
public void SetPosition(Vector3 position, Vector3 reference)
{
normalVector = new Vector3(0, 0, 1); //reference - position;
normalVector.Normalize();
upVector = new Vector3(0.0f, 1.0f, 0.0f);
perpVector = new Vector3(-1.0f, 0.0f, 0.0f);//Vector3.Cross(normalVector, upVector); //new Vector3(-1.0f, 0.0f, 0.0f);
upVector = Vector3.Cross(perpVector, normalVector);
// Look in Z+ direction
currentRotation = -Quaternion.Identity;
Vector2 unitHoriz = new Vector2(0, 1);
Vector2 unitVert = new Vector2(1, 0);
Vector3 N = reference - position;
N.Normalize();
Vector2 Nhoriz = new Vector2(N.X, N.Z);
Vector2 Nvert = new Vector2(N.Z, N.Y);
float angleHoriz = (float)Math.Acos((double)(Vector2.Dot(unitHoriz, Nhoriz)));
float anglVert = 0.0f;// (float)Math.Acos((double)(Vector2.Dot(unitVert, Nvert)));
//Determine the sign of the angle needed
Vector3 cross = Vector3.Cross(Vector3.UnitZ, N);
if (cross.Y <= 0)
{
angleHoriz = -Math.Abs(angleHoriz);
}
else
{
angleHoriz = Math.Abs(angleHoriz);
}
//passing negative because I am negating it again inside the function
Rotate(anglVert, angleHoriz);
// perpVector = Vector3.Cross(normalVector, upVector);
//upVector = Vector3.Cross(perpVector, normalVector);
currentPosition = new Vector3() + position;
lastPosition = position - normalVector;
}
///
/// Add an external force to be applied to the mass
///
/// Force to apply
public void AddForce(Vector3 force)
{
totalForce += force;
if (float.IsNaN(totalForce.X))
{
Logging.addNan();
System.Diagnostics.Debug.WriteLine("AddForce.. totalForce = NaN");
totalForce = Vector3.Zero;
}
}
///
/// Add force caused due to friction
///
/// Total frictional coefficients
/// Normal force
/// Tangential direction of motion
public void AddFriction(float totalFriction, float perpendicularForce, Vector3 tangentialDirection)
{
if (tangentialDirection.Length() != 0.0f)
{
tangentialDirection.Normalize();
}
totalForce += (totalFriction * Math.Abs(perpendicularForce)) * tangentialDirection;
if (float.IsNaN(totalForce.X))
{
Logging.addNan();
//throw new Exception("NaN Error.");
System.Diagnostics.Debug.WriteLine("AddFriction.. totalForce = NaN");
totalForce = Vector3.Zero;
}
}
///
/// Function to add/remove energy from the object
///
private void UpdateEnergy()
{
// Add newEnergy to energy and reset newEnergy
energy += newEnergy;
newEnergy = 0.0f;
// Bounds checking
if (energy > 1.0f)
{
energy = 1.0f;
}
else if (energy < -1.0f)
{
energy = -1.0f;
}
}
///
/// Function to set the energy state of an object
///
/// Energy state of object
private void SetEnergy(float newEnergyValue)
{
energy = newEnergyValue;
}
///
/// Add/Remove energy to the mass
///
/// Change in energy
public void ChangeEnergy(float amount)
{
newEnergy += amount;
}
///
/// Move the player based on the controller inputs
///
/// Force applied along the normal
/// Force applied along the tangent
public void MoveObject(float dn, float dx)
{
if (movementType == Enums_Engine.MovementType.WALK)
{
float moveRatioN = dn * Statics_Engine.PlayerSettings.WALK_SPEED;
float moveRatioX = dx * Statics_Engine.PlayerSettings.WALK_SPEED;
//Statics.lookupMap.RemoveCurrent(this);
sensorDirection = Vector3.Zero;
if (dn != 0)
{
sensorDirection += new Vector3(normalVector.X, 0, normalVector.Z) * moveRatioN;
}
if (dx != 0)
{
sensorDirection += new Vector3(perpVector.X, 0, perpVector.Z) * moveRatioX;
}
lastPosition = currentPosition;
currentPosition += sensorDirection;
EnforceLevelBoundary();
}
else
{
float moveRatioN = dn * Statics_Engine.PlayerSettings.HOVER_SPEED;
float moveRatioX = dx * Statics_Engine.PlayerSettings.HOVER_SPEED;
if (Math.Abs(totalForce.X) + Math.Abs(totalForce.Z) < 5000.0f)
{
// Add forward/backward force to mass object
AddForce( normalVector * moveRatioN);
// Add left/right force to mass object
AddForce(perpVector * moveRatioX);
}
}
}
///
/// Force applied when the player hits something
///
/// Amount of rebound force
public void KickBack(float amount)
{
if (movementType == Enums_Engine.MovementType.WALK && amount != 0.0f)
{
//Statics.lookupMap.RemoveCurrent(this);
lastPosition = currentPosition;
currentPosition += new Vector3(-sensorDirection.X, 0, -sensorDirection.Z) * amount;
EnforceLevelBoundary();
}
else if (movementType == Enums_Engine.MovementType.HOVER)
{
velocity += -velocity * amount;
}
}
///
/// Rotate the coordinate system
///
/// Vertical rotation
/// Horizontal rotation
public void Rotate(float dVert, float dHoriz)
{
Quaternion vertRotation = Quaternion.CreateFromAxisAngle(perpVector, dVert);
Quaternion horizRotation = Quaternion.CreateFromAxisAngle(upVector, -dHoriz);
Quaternion totalRotation;
Vector3 newNormal = Vector3.Transform(normalVector, Matrix.CreateFromQuaternion(horizRotation * vertRotation));
// Limit the rotation
double angle1 = Math.Acos((double)(Vector3.Dot(normalVector, new Vector3(0, 1, 0))));
double angle2 = Math.Acos((double)(Vector3.Dot(normalVector, newNormal)));
double angle;
if (dVert > 0)
{
angle = angle1 - angle2;
}
else
{
angle = angle1 + angle2;
}
// Limit vertical (up/down) rotation
if (angle < Statics_Engine.PlayerSettings.PLAYER_ROTATION_CAP_UP || angle > Statics_Engine.PlayerSettings.PLAYER_ROTATION_CAP_DOWN)
{
//ignore the vertical rotation
totalRotation = horizRotation;// *(-vertRotation);
}
else
{
//do both rotations
totalRotation = horizRotation * vertRotation;
}
currentRotation = Quaternion.Normalize(totalRotation * currentRotation);
normalVector = Vector3.Transform(normalVector, Matrix.CreateFromQuaternion(totalRotation));
perpVector = Vector3.Transform(perpVector, Matrix.CreateFromQuaternion(totalRotation));
}
}
}
/* Legacy Code
//Strafe object on the V (sideways) plane
public void Strafe(float dx)
{
if (movementType == MovementType.WALK && dx != 0.0f)
{
Statics.lookupMap.RemoveCurrent(this);
float moveRatio = dx / Statics.PlayerSettings.PLAYER_SPEED_ON_THE_GROUND;
lastPosition = currentPosition;
sensorDirection = new Vector3(perpVector.X, 0, perpVector.Z) * moveRatio * Statics.PlayerSettings.PLAYER_WALK_SPEED;
currentPosition += sensorDirection;
EnforceBoundary();
}
else
{
AddForce(perpVector * dx);
}
}
//Move forwards or backwards along normal (does not restrict Y value)
public void Move(float dn)
{
if (movementType == MovementType.WALK && dn != 0.0f)
{
Statics.lookupMap.RemoveCurrent(this);
float moveRatio = dn / Statics.PlayerSettings.PLAYER_SPEED_ON_THE_GROUND;
lastPosition = currentPosition;
sensorDirection = new Vector3(normalVector.X, 0, normalVector.Z) * moveRatio * Statics.PlayerSettings.PLAYER_WALK_SPEED;
currentPosition += sensorDirection;
EnforceBoundary();
}
else
{
AddForce(normalVector * dn);
}
}
*/