//--------------------------------------------------------------------------------------------------------------------------------------------------- // // Copyright (C)2007 DarkWynter Studios. All rights reserved. // //--------------------------------------------------------------------------------------------------------------------------------------------------- // {License Information: Creative Commons} //--------------------------------------------------------------------------------------------------------------------------------------------------- #region Comments /* * We'll need to interface Collision when we do gpgpu collision * This way we can call either cpu or gpu configurations dependent on machine caps * * This will also provide us a clean break for a dll * * */ #endregion namespace ElementalGame { #region Using Statements using System; using System.Collections.Generic; using System.Threading; using System.Diagnostics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion // Main class where all of our object collisions are handled place public class Collision { // Attributes: Vector3 tangentialComponent = new Vector3(); Vector3 normalComponent = new Vector3(); float timeInterval; Vector3 normalForce1, normalForce2; // Stores each object's normal force component //constant max mass private const float MAXIMUM_MASS = 35; // Constructor: public Collision() { } // Calls all the Collision functions in order public void UpdateCollision(ObjectLibrary objectLibrary) { timeInterval = ElementalGame.dt; List newDynamicObject = new List(); List dynamicMasses = ObjectLibrary.lookupMap.GetDynamicList(); bool deletedDynamic = false; for (int i = 0; i < objectLibrary.humans.Count; i++) { objectLibrary.humans[i].hitWhere.Clear(); } for (int i = 0; i < objectLibrary.bots.Count; i++) { objectLibrary.bots[i].hitWhere.Clear(); } // For each moving GameObject for (int i = 0; i < dynamicMasses.Count; i++) { // Need to clear out some values for collision dynamicMasses[i].objectHitBy.Clear(); // For each object in the vacinity of the gameObject LinkedList collidingObjects = ObjectLibrary.lookupMap.GetCollisionObjects(dynamicMasses[i]); LinkedListNode node = collidingObjects.First; if (collidingObjects.Count != 0 && dynamicMasses[i].gameObjectPointer != null) { Mass cObject; do { cObject = node.Value; // If not already checked if (!cObject.collisionChecked && cObject.gameObjectPointer != null) { // If collided if (dynamicMasses[i].gameObjectPointer.boundingSphere.Intersects(cObject.gameObjectPointer.boundingSphere)) { // Check if the colliding object is already in our dynamic list, if not then add it to our list of objects which need // to be added to the dynamic list if (!dynamicMasses.Contains(cObject) && !newDynamicObject.Contains(cObject)) { newDynamicObject.Add(cObject); } // Cohesion if (dynamicMasses[i].objectType == Mass.ObjectType.PARTICLE && cObject.objectType == Mass.ObjectType.PARTICLE) { // Particle Interaction if (ParticleInteraction(dynamicMasses[i], cObject)) { if (i < dynamicMasses.Count) { deletedDynamic = true; ObjectTerrainCollision(dynamicMasses[i], (int)dynamicMasses[i].objectHeight, objectLibrary.terrain); i--; } else { i--; ObjectTerrainCollision(dynamicMasses[i], (int)dynamicMasses[i].objectHeight, objectLibrary.terrain); } break; } } // Collectable Objects else if (dynamicMasses[i].objectType == Mass.ObjectType.PLAYER && cObject.gameObjectPointer.isCollectable) { // Increment Number of Keys found Player player = (Player)dynamicMasses[i].gameObjectPointer; if (player.IsHuman()) { Audio.KeyFound(); if (GameFlow.gameType == GameFlow.GameType.STORY_MODE) { player.numberOfKeysFound++; } player.ChangeHealth(XML.GameSettings.HEALTH_BOOST_FOR_KEY, objectLibrary); ObjectLibrary.lookupMap.RemoveLast(cObject); ObjectLibrary.lookupMap.RemoveCurrent(cObject); if (ObjectLibrary.lookupMap.DynamicContains(cObject)) { ObjectLibrary.lookupMap.RemoveDynamic(cObject); } cObject.ClearValues(); cObject.deadObject = true; ((Human)player).ShowKeyIndicator(); } } else if (dynamicMasses[i].gameObjectPointer.isCollectable && cObject.objectType == Mass.ObjectType.PLAYER) { // Increment Number of Keys found Player player = (Player)cObject.gameObjectPointer; if (player.IsHuman()) { player.numberOfKeysFound++; ObjectLibrary.lookupMap.RemoveLast(dynamicMasses[i]); ObjectLibrary.lookupMap.RemoveCurrent(dynamicMasses[i]); if (ObjectLibrary.lookupMap.DynamicContains(dynamicMasses[i])) { ObjectLibrary.lookupMap.RemoveDynamic(dynamicMasses[i]); } dynamicMasses[i].ClearValues(); dynamicMasses[i].deadObject = true; ((Human)player).ShowKeyIndicator(); // Remove Collectable from list and give to player if (i < dynamicMasses.Count) { deletedDynamic = true; i--; } else { i--; } break; } } else { // Adhesion //else if ((dynamicMasses[i].objectType == Mass.ObjectType.PARTICLE && cObject != Mass.ObjectType.PARTICLE)|| // (dynamicMasses[i].objectType != Mass.ObjectType.PARTICLE && cObject == Mass.ObjectType.PARTICLE) ) //{ //} // Handle collision physics ObjectObjectCollision(dynamicMasses[i], cObject); if ((dynamicMasses[i].objectType == Mass.ObjectType.PLAYER && cObject.objectType == Mass.ObjectType.PARTICLE) || (dynamicMasses[i].objectType == Mass.ObjectType.PARTICLE && cObject.objectType == Mass.ObjectType.PLAYER)) { if (dynamicMasses[i].objectType == Mass.ObjectType.PLAYER) { if (((Player)dynamicMasses[i].gameObjectPointer).IsAlive()) { PlayerHitByParticle((Player)dynamicMasses[i].gameObjectPointer, normalForce1, (Particle)cObject.gameObjectPointer, normalForce2, objectLibrary); Player player = (Player)dynamicMasses[i].gameObjectPointer; if (player.IsAlive() == false) { AssignKillCredit(player, objectLibrary); } } } else { if (((Player)cObject.gameObjectPointer).IsAlive()) { if (PlayerHitByParticle((Player)cObject.gameObjectPointer, normalForce2, (Particle)dynamicMasses[i].gameObjectPointer, normalForce1, objectLibrary)) { Player player = (Player)cObject.gameObjectPointer; if (player.IsAlive() == false) { AssignKillCredit(player, objectLibrary); } if (i < dynamicMasses.Count) { deletedDynamic = true; ObjectTerrainCollision(dynamicMasses[i], (int)dynamicMasses[i].objectHeight, objectLibrary.terrain); i--; } else { i--; ObjectTerrainCollision(dynamicMasses[i], (int)dynamicMasses[i].objectHeight, objectLibrary.terrain); } break; } } } } } } } node = node.Next; } while (node != null); if (deletedDynamic) { deletedDynamic = false; continue; } // Mark that object has been checked for collisions dynamicMasses[i].collisionChecked = true; } //////////////////////////////////////// //only do for player for now if (dynamicMasses[i].objectType == Mass.ObjectType.PLAYER)// || dynamicMasses[i].objectType == Mass.ObjectType.PARTICLE) { Sensor collisionSensor; float kickBackRatio = 2.0f; collisionSensor = ((Player)dynamicMasses[i].gameObjectPointer).collisionSensor; float heightDifference = ObjectTerrainCollision(collisionSensor.mass, (int)dynamicMasses[i].objectHeight, objectLibrary.terrain); Vector3 sensorDiff = collisionSensor.mass.currentPosition - dynamicMasses[i].currentPosition; //if primarily pointing down.. ignore if (sensorDiff.Y < 0 && (Math.Abs(sensorDiff.Y) > Math.Abs(sensorDiff.X) && Math.Abs(sensorDiff.Y) > Math.Abs(sensorDiff.Z))) { } else if (heightDifference >= XML.PlayerSettings.DEFAULT_PLAYER_HEIGHT) { // Apply a negative velocity force to the player to slow them down float sensorDistance = sensorDiff.Length(); dynamicMasses[i].KickBack(kickBackRatio); dynamicMasses[i].objectHitBy.Add(Mass.TerrainType.TERRAIN_GROUND); } } // Check if particle and not moving much //else if (dynamicMasses[i].objectType == Mass.ObjectType.PARTICLE) //{ // if (dynamicMasses[i].velocity.Length() < 50.0f) // { // if (dynamicMasses[i].mass > 0.1f) // { // //shrink the particle // dynamicMasses[i].mass -= 0.001f; // float massRatio = (dynamicMasses[i].mass / 75.0f); // dynamicMasses[i].scale = massRatio * 30.0f + 3;//old way: particleMassValue * 0.50f; // } // else // { // //Get rid of it altogether // ObjectLibrary.lookupMap.RemoveLast(dynamicMasses[i]); // ObjectLibrary.lookupMap.RemoveCurrent(dynamicMasses[i]); // if (ObjectLibrary.lookupMap.DynamicContains(dynamicMasses[i])) // { // ObjectLibrary.lookupMap.RemoveDynamic(dynamicMasses[i]); // dynamicMasses[i].ClearValues(); // } // dynamicMasses[i].ClearValues(); // } // } //} ObjectTerrainCollision(dynamicMasses[i], (int)dynamicMasses[i].objectHeight, objectLibrary.terrain); } ObjectLibrary.lookupMap.AddRange(newDynamicObject); } public static void AssignKillCredit(Player killedPlayer, ObjectLibrary objectLibrary) { // Insert killed by stuff here // They were alive going into this method if they come out dead then // We know someone killed them and can assign credit // If the player did not suicide find out who killed them if (killedPlayer.killedBy != killedPlayer.playerIndex) { if (killedPlayer.killedBy < 4) { // Find the killer who is human for (int i = 0; i < objectLibrary.humans.Count; i++) { // Assign credit to the killer if (killedPlayer.killedBy == objectLibrary.humans[i].playerIndex) { objectLibrary.humans[i].kills++; } } } else { // Find the bot that killed for (int i = 0; i < objectLibrary.bots.Count; i++) { // Assign credit to the killer if (killedPlayer.killedBy == objectLibrary.bots[i].playerIndex) { objectLibrary.bots[i].kills++; } } } } else { killedPlayer.kills--; } } private bool ParticleInteraction(Mass mass1, Mass mass2) { Vector3 object1NormalVelocity = new Vector3(); // Stores the first object's normal velocity Vector3 object2NormalVelocity = new Vector3(); // Stores the second object's normal velocity Vector3 totalNormalVelocity = new Vector3(); // Stores the sum of both object's normal velocities float stickiness; float totalMass = mass1.mass + mass2.mass; // Sum of both masses Vector3 normalVectorOfImpact = new Vector3(); // Stores the normal vector of impact/contact Particle particle1 = ((Particle)mass1.gameObjectPointer); Particle particle2 = ((Particle)mass1.gameObjectPointer); if (particle1.particleType == particle2.particleType) { stickiness = (mass1.cohesion + mass2.cohesion) / 2.0f; } else { stickiness = (mass1.adhesion + mass2.adhesion) / 2.0f; } // Check which mass has the greater velocity and calculate the normal based on that if (mass1.velocity.Length() >= mass2.velocity.Length()) { // Mass1 has higher velocity so impact vector is mass2 - mass1, ie, from mass1 to mass2 normalVectorOfImpact = mass2.currentPosition - mass1.currentPosition; if (normalVectorOfImpact != Vector3.Zero) { normalVectorOfImpact.Normalize(); } // Ensure that mass1's velocity is not equal to 0 if (mass1.velocity.Length() != 0.0f) { // Check that the velocity is not moving away from the impact, ie, the angle between velocity // and impact vector is between 90->0->270. If so the two objects are moving away from each // other, so return. if (Vector3.Dot(mass1.velocity, normalVectorOfImpact) / mass1.velocity.Length() < 0.0f) { return false; } } // Calculate Mass1's normal velocity component CalculateVectorComponents(normalVectorOfImpact, mass1.velocity); object1NormalVelocity = normalComponent; // Calculate Mass2's normal velocity component CalculateVectorComponents(-normalVectorOfImpact, mass2.velocity); object2NormalVelocity = normalComponent; // Find the difference between these two velocity to give our final resultant velocity totalNormalVelocity = object1NormalVelocity - object2NormalVelocity; } else { // Mass2 has higher velocity so impact vector is mass1 - mass2, ie, from mass2 to mass1 normalVectorOfImpact = mass1.currentPosition - mass2.currentPosition; if (normalVectorOfImpact != Vector3.Zero) { normalVectorOfImpact.Normalize(); } if (mass2.velocity.Length() != 0.0f) { // Check that the velocity is not moving away from the impact, ie, the angle between velocity // and impact vector is between 90->0->270. If so the two objects are moving away from each // other, so return. if (Vector3.Dot(mass2.velocity, normalVectorOfImpact) / mass2.velocity.Length() < 0.0f) { return false; } } // Calculate Mass1's normal velocity component CalculateVectorComponents(normalVectorOfImpact, mass1.velocity); object1NormalVelocity = normalComponent; // Calculate Mass2's normal velocity component CalculateVectorComponents(-normalVectorOfImpact, mass2.velocity); object2NormalVelocity = normalComponent; // Find the difference between these two velocity to give our final resultant velocity totalNormalVelocity = object2NormalVelocity - object1NormalVelocity; } if (totalNormalVelocity.Length() >= 15.0f) { normalForce1 = 2.0f * mass2.mass * totalNormalVelocity * stickiness / (timeInterval * totalMass); normalForce2 = 2.0f * mass1.mass * -totalNormalVelocity * stickiness / (timeInterval * totalMass); // Add the forces to the respective masses mass1.AddForce(normalForce1); mass2.AddForce(normalForce2); mass1.isMoving = true; mass2.isMoving = true; } else { if (((Particle)mass1.gameObjectPointer).particleType == ((Particle)mass2.gameObjectPointer).particleType) { //Some particles can't combine.. //Earth boulders don't combine if ((((Particle)mass1.gameObjectPointer).particleType == Particle.ParticleType.Earth && (mass1.energy < 0.2f && mass2.energy < 0.2f)) == false && //Ice balls can't combine (((Particle)mass1.gameObjectPointer).particleType == Particle.ParticleType.Water && (mass1.energy < -0.2f && mass2.energy < -0.2f)) == false && //Make sure things don't get TOO big (mass1.mass + mass2.mass) < MAXIMUM_MASS) { mass1.energy = ((mass1.energy * mass1.mass) + (mass2.energy * mass2.mass)) / (mass1.mass + mass2.mass); mass1.currentPosition = ((mass1.currentPosition * mass1.mass) + (mass2.currentPosition * mass2.mass)) / (mass1.mass + mass2.mass); mass1.lastPosition = ((mass1.lastPosition * mass1.mass) + (mass2.lastPosition * mass2.mass)) / (mass1.mass + mass2.mass); mass1.totalForce = ((mass1.totalForce * mass1.mass) + (mass2.totalForce * mass2.mass)) / (mass1.mass + mass2.mass); mass1.velocity = ((mass1.velocity * mass1.mass) + (mass2.velocity * mass2.mass)) / (mass1.mass + mass2.mass); mass1.objectHeight = mass1.objectHeight + mass2.objectHeight; mass1.mass += mass2.mass; mass1.scale = mass1.scale + mass2.scale; //a hack for fixing the bounding sphere issue particle1._boundingSphere.Radius = particle1._boundingSphere.Radius + particle2._boundingSphere.Radius; particle1._boundingSphere.Radius *= 0.8f; ObjectLibrary.lookupMap.RemoveLast(mass2); ObjectLibrary.lookupMap.RemoveCurrent(mass2); if (ObjectLibrary.lookupMap.DynamicContains(mass2)) { ObjectLibrary.lookupMap.RemoveDynamic(mass2); mass2.ClearValues(); mass2.deadObject = true; return true; } mass2.ClearValues(); mass2.deadObject = true; return false; } normalForce1 = 2.0f * mass2.mass * -totalNormalVelocity * stickiness / (timeInterval * totalMass); normalForce2 = 2.0f * mass1.mass * totalNormalVelocity * stickiness / (timeInterval * totalMass); // Add the forces to the respective masses mass1.AddForce(normalForce1); mass2.AddForce(normalForce2); mass1.isMoving = true; mass2.isMoving = true; } } return false; } private bool PlayerHitByParticle(Player player, Vector3 playerCollisionForce, Particle particle, Vector3 particleCollisionForce, ObjectLibrary objectLibrary) { //Vector3 normalizedVelocity = particle.mass.velocity + Vector3.Zero; //normalizedVelocity.Normalize(); //Ray particleRay = new Ray(particle.mass.lastPosition, normalizedVelocity); //float? distanceToImpact = player.boundingSphere.Intersects(particleRay); //float distanceMoved = Vector3.Distance(particle.mass.currentPosition, particle.mass.lastPosition); //bool intersects = false; //if (distanceToImpact != null) //{ // if ((float)distanceToImpact <= distanceMoved) // { // intersects = true; // } //} //if (player.boundingSphere.Intersects(particle.boundingSphere)) //{ // intersects = true; //} if ((particle.hasCollided != player.playerIndex && particle.ownerID != player.playerIndex) || (particle.hurtOwner == true && particle.ownerID == player.playerIndex)) { // We know player is hit... determine which general direction Vector3 toParticle = new Vector3(particle.mass.currentPosition.X - player.mass.currentPosition.X, particle.mass.currentPosition.Y - player.mass.currentPosition.Y, particle.mass.currentPosition.Z - player.mass.currentPosition.Z); player.SetHitShader(toParticle, particle); Vector3 playerLook = new Vector3(player.mass.normalVector.X, 0, player.mass.normalVector.Z); playerLook.Normalize(); Vector3 cross = Vector3.Cross(toParticle, playerLook); if (cross.Y < 0) { player.hitWhere.Add(Player.HitWhere.LEFT); } else { player.hitWhere.Add(Player.HitWhere.RIGHT); } // Player shielded properly.. no damage and absorbs the particle if ((player.activeShield == Player.ShieldType.EARTH_SHIELD && particle.particleType == Particle.ParticleType.Earth) || (player.activeShield == Player.ShieldType.WATER_SHIELD && particle.particleType == Particle.ParticleType.Water) || (player.activeShield == Player.ShieldType.WIND_SHIELD && particle.particleType == Particle.ParticleType.Air)) { // Correct shield -> player absorbs manna player.ChangeManna(XML.PlayerSettings.SHIELD_MANNA_GAIN * particle.mass.mass); if (player.IsHuman()) { ((Human)player).ShowMannaIndicator(); } //Get rid of it altogether ObjectLibrary.lookupMap.RemoveLast(particle.mass); ObjectLibrary.lookupMap.RemoveCurrent(particle.mass); if (ObjectLibrary.lookupMap.DynamicContains(particle.mass)) { ObjectLibrary.lookupMap.RemoveDynamic(particle.mass); } particle.mass.ClearValues(); particle.mass.deadObject = true; return true; } else { // Wrong shield -> player loses health //player.ChangeHealth(-XML.PlayerSettings.DIRECT_HEALTH_LOSS * particle.mass.scale); player.ChangeHealth(DamageCalculations(particle.mass, player.mass), objectLibrary); // If tornado.. apply random force! if (particle.particleType == Particle.ParticleType.Air) { Random rand = new Random(); float multiplier = particle.mass.mass * 100000; player.mass.AddForce(new Vector3(0, multiplier, 0)); } // If the player is dead and they haven't been assigned who killed them then do so // Also need to set their killed by info if (!player.IsAlive() && player.killedBy == -1) { player.Kill(particle.ownerID); if (player.playerIndex < 4) { //Its a human.. set its killedby HUD info ((Human)player).SetKilledByInfo(particle); } } } particle.hasCollided = player.playerIndex; particle.hasCollidedTimer = Stopwatch.StartNew(); } return false; } public float DamageCalculations(Mass particleMass, Mass playerMass) { float damage = 0.0f; Particle particle = ((Particle)particleMass.gameObjectPointer); // ~ about between 0 and 1 for slow to fast float particleVelocityRatio = particleMass.velocity.Length() / 2000.0f; // Velocity.Length() range~ // Earth: 200-1600 // Water: 400-1700 // Air: 500-1700 // Particle type based damage modifier if (particle.particleType == Particle.ParticleType.Earth) { if (particleMass.energy > 0.3f) { //lava hurts more if (particleMass.velocity.Length() < 50) { //its slower.. it hurts more? damage = 25.0f; } else { damage = 10.0f; } } else { //frozen rock and rock are the same damage = 5.0f + particleVelocityRatio; } } else if (particle.particleType == Particle.ParticleType.Water) { if (particleMass.energy > 0.5f) { //boiling water hurts alot if (particleMass.velocity.Length() < 50) { damage = 25.0f; } else { damage = 20.0f; } } else if (particleMass.energy < 0.0f) { //frozen ice hurts a bit more damage = 3.0f + particleVelocityRatio; } else { //regular water doesn't hurt that much if (particleMass.velocity.Length() < 50) { //slow water doesn't hurt damage = 0.0f; } else { damage = 10.0f; } } } else //air particle is all the same { damage = 0.1f + particleVelocityRatio; } float totalDamage = -damage * (1 + (particleMass.mass / 5.0f)) * XML.PlayerSettings.DIRECT_HEALTH_LOSS; Debug.WriteLine("Particle type=" + particle.particleType.ToString() + ",mass=" + particleMass.mass + ", vRatio=" + particleVelocityRatio + ", v=" + particleMass.velocity.Length() + " damage=" + damage + "...total= " + totalDamage + ".... energy=" + particleMass.energy); return totalDamage; } // Handles collisions between two objects. Note: All references to normal in this function refers to the normal of impact private void ObjectObjectCollision(Mass mass1, Mass mass2) { //check to see if its a player and a liquid particle //check if mass1 is the player if (mass1.objectType == Mass.ObjectType.PLAYER && mass2.objectType == Mass.ObjectType.PARTICLE) { Particle particle = (Particle)mass2.gameObjectPointer; if (particle.particleType == Particle.ParticleType.Water && particle.mass.energy > 0 && particle.mass.energy < 0.5) { //its regular water return; } } //check if mass2 is the player if (mass2.objectType == Mass.ObjectType.PLAYER && mass1.objectType == Mass.ObjectType.PARTICLE) { Particle particle = (Particle)mass1.gameObjectPointer; if (particle.particleType == Particle.ParticleType.Water && particle.mass.energy > 0 && particle.mass.energy < 0.5) { //its regular water return; } } Vector3 object1NormalVelocity = new Vector3(); // Stores the first object's normal velocity Vector3 object2NormalVelocity = new Vector3(); // Stores the second object's normal velocity Vector3 totalNormalVelocity = new Vector3(); // Stores the sum of both object's normal velocities float totalMass = mass1.mass + mass2.mass; // Sum of both masses Vector3 normalVectorOfImpact = new Vector3(); // Stores the normal vector of impact/contact // Check which mass has the greater velocity and calculate the normal based on that if (mass1.velocity.Length() >= mass2.velocity.Length()) { // Mass1 has higher velocity so impact vector is mass2 - mass1, ie, from mass1 to mass2 normalVectorOfImpact = mass2.currentPosition - mass1.currentPosition; if (normalVectorOfImpact != Vector3.Zero) { normalVectorOfImpact.Normalize(); } // Ensure that mass1's velocity is not equal to 0 if (mass1.velocity.Length() != 0.0f) { // Check that the velocity is not moving away from the impact, ie, the angle between velocity // and impact vector is between 90->0->270. If so the two objects are moving away from each // other, so return. if (Vector3.Dot(mass1.velocity, normalVectorOfImpact) / mass1.velocity.Length() < 0.0f) { return; } } // Calculate Mass1's normal velocity component CalculateVectorComponents(normalVectorOfImpact, mass1.velocity); object1NormalVelocity = normalComponent; // Calculate Mass2's normal velocity component CalculateVectorComponents(-normalVectorOfImpact, mass2.velocity); object2NormalVelocity = normalComponent; // Find the difference between these two velocity to give our final resultant velocity totalNormalVelocity = object1NormalVelocity - object2NormalVelocity; // Based on In-elastic collision v1 = (m2 * totalforce)/(m1 + m2). The rest of the stuff is // to convert a velocity into a force. normalForce1 = 2.0f * mass2.mass * -totalNormalVelocity / (timeInterval * totalMass); normalForce2 = 2.0f * mass1.mass * totalNormalVelocity / (timeInterval * totalMass); } else { // Mass2 has higher velocity so impact vector is mass1 - mass2, ie, from mass2 to mass1 normalVectorOfImpact = mass1.currentPosition - mass2.currentPosition; if (normalVectorOfImpact != Vector3.Zero) { normalVectorOfImpact.Normalize(); } if (mass1.velocity.Length() != 0.0f) { // Check that the velocity is not moving away from the impact, ie, the angle between velocity // and impact vector is between 90->0->270. If so the two objects are moving away from each // other, so return. if (Vector3.Dot(mass2.velocity, normalVectorOfImpact) / mass2.velocity.Length() < 0.0f) { return; } } // Calculate Mass1's normal velocity component CalculateVectorComponents(normalVectorOfImpact, mass1.velocity); object1NormalVelocity = normalComponent; // Calculate Mass2's normal velocity component CalculateVectorComponents(-normalVectorOfImpact, mass2.velocity); object2NormalVelocity = normalComponent; // Find the difference between these two velocity to give our final resultant velocity totalNormalVelocity = object2NormalVelocity - object1NormalVelocity; // Based on In-elastic collision v1 = (m2 * totalforce)/(m1 + m2). The rest of the stuff is // to convert a velocity into a force. normalForce1 = 2.0f * mass2.mass * totalNormalVelocity / (timeInterval * totalMass); normalForce2 = 2.0f * mass1.mass * -totalNormalVelocity / (timeInterval * totalMass); } // Add the forces to the respective masses mass1.AddForce(normalForce1); mass2.AddForce(normalForce2); mass1.numberOfCollision++; mass2.numberOfCollision++; // Set each mass's boolean check so that it will get updated next cycle mass1.isMoving = true; mass2.isMoving = true; } // Collisions between an object and the terrain. The float return value is the height difference of the object and the terrain public float ObjectTerrainCollision(Mass mass, int objectHeight, Terrain terrain) { // Used to calculate the average terrain height around the player float totalHeight = 0; // Get X and Z positions int posX = (int)mass.currentPosition.X; int posZ = (int)mass.currentPosition.Z; // Make sure we don't go out of bounds if (posX <= 0) { posX = 1; } if (posZ <= 0) { posZ = 1; } if (posX >= Terrain.collisionMapSize) { posX = Terrain.collisionMapSize - 1; } if (posZ >= Terrain.collisionMapSize) { posZ = Terrain.collisionMapSize - 1; } // Add up all neighboring terrain points totalHeight = terrain.GetTerrainHeight(posX / Terrain.terrainScaleFactor, posZ / Terrain.terrainScaleFactor); // Average all the neighboring terrain points float newHeight = totalHeight + (objectHeight / 2); // Get difference between average and player float heightDifference = newHeight - mass.currentPosition.Y; Vector3 totalForce = mass.totalForce + Vector3.Zero; Vector3 velocity = mass.velocity + Vector3.Zero; if (mass.objectType == Mass.ObjectType.SENSOR) { return heightDifference; } // If terrain and player are touching if (heightDifference < 0.0f) { mass.AddForce(mass.mass * Mass.accelDueToGravity); } else if (heightDifference > 0.0f) { Vector3 surfaceNormal = terrain.GetTerrainNormal(posX / Terrain.terrainScaleFactor, posZ / Terrain.terrainScaleFactor, mass.velocity); //Vector3.Zero + terrain.vertices[(int)((posZ / Terrain.terrainScaleFactor) + // (posX / Terrain.terrainScaleFactor) * Terrain.vertexMapSize) // ].Normal; // Calculate the components of gravity with reference to surface normal CalculateVectorComponents(surfaceNormal, Mass.accelDueToGravity); Vector3 gravityNormalComponent = normalComponent; Vector3 gravityTangentialComponent = tangentialComponent; // Resolve all forces (gravity n totalForce) and velocity into their normal and tangential components based on the surface normal // Calculate the components of our velocity with reference to the surface normal CalculateVectorComponents(surfaceNormal, velocity); Vector3 velocityNormalComponent = normalComponent; Vector3 velocityTangentialComponent = tangentialComponent; CalculateVectorComponents(surfaceNormal, totalForce); Vector3 totalForceNormalComponent = normalComponent; Vector3 totalForceTangentialComponent = tangentialComponent; Vector3 combinedNormalForce = (mass.mass * velocityNormalComponent / timeInterval) + totalForceNormalComponent; Vector3 combinedTangentialForce = (mass.mass * velocityTangentialComponent / timeInterval) + totalForceTangentialComponent + (mass.mass * gravityTangentialComponent); mass.numberOfCollision++; if (mass.objectType == Mass.ObjectType.PARTICLE) { if (combinedNormalForce.Y < 0.0f) { mass.AddForce(-combinedNormalForce); } else { mass.AddForce(-velocityNormalComponent); } mass.AddForce(heightDifference * surfaceNormal * terrain.particleSurfaceTension * mass.COR); if (combinedTangentialForce.Length() >= 0.5f) { mass.AddForce(mass.mass * -gravityTangentialComponent); mass.AddFriction(mass.dynamicFrictionCoefficient + terrain.mass.dynamicFrictionCoefficient, (mass.mass * gravityNormalComponent) + totalForceNormalComponent, combinedTangentialForce); } else { mass.AddForce(mass.mass * -velocityTangentialComponent / timeInterval); mass.AddForce(-totalForceTangentialComponent); } } else { if (totalForce.Y > 0.0f) { totalForce.Y = 0.0f; } if (velocity.Y > 0.0f) { velocity.Y = 0.0f; } if (mass.objectType == Mass.ObjectType.PLAYER) { if (mass.lastMaxHeight - totalHeight > 1000 && velocity.Length() > 500) { // Player hit the terrain hard while falling mass.objectHitBy.Add(Mass.TerrainType.TERRAIN_GROUND); mass.fallingDamageMultiplier = (mass.lastMaxHeight - totalHeight) / 100; } if (heightDifference < XML.PlayerSettings.DEFAULT_PLAYER_HEIGHT / 4.0f) { // Player hit a terrain thats low mass.objectHitBy.Add(Mass.TerrainType.TERRAIN_LOW); } else if (heightDifference < XML.PlayerSettings.DEFAULT_PLAYER_HEIGHT / 3.0f) { // Player hit a terrain thats relatively high mass.objectHitBy.Add(Mass.TerrainType.TERRAIN_MEDIUM); } else if (heightDifference >= XML.PlayerSettings.DEFAULT_PLAYER_HEIGHT) { // Player hit a terrain wall mass.objectHitBy.Add(Mass.TerrainType.TERRAIN_HIGH); } // Ground pushing up to negate gravity if (mass.movementType == Mass.MovementType.WALK) { //only if its NOT a wall if (heightDifference < XML.PlayerSettings.DEFAULT_PLAYER_HEIGHT) { ObjectLibrary.lookupMap.RemoveLast(mass); ObjectLibrary.lookupMap.RemoveCurrent(mass); //just set their height! mass.currentPosition.Y = totalHeight + (objectHeight / 2); ObjectLibrary.lookupMap.SetPosition(mass); //negate forces on player mass.velocity = Vector3.Zero; mass.totalForce = Vector3.Zero; } else { //mass.KickBack(5.0f); //mass.currentPosition = mass.lastPosition + Vector3.Zero; } } else if(mass.movementType == Mass.MovementType.HOVER) { mass.AddForce((heightDifference * terrain.playerSurfaceTension * surfaceNormal) - (new Vector3(0.0f, totalForce.Y, 0.0f) + new Vector3(0.0f, velocity.Y * mass.mass / timeInterval, 0.0f))); if (velocityTangentialComponent.Length() > 10.0f) { //if (mass.velocity.Length() < XML.PlayerSettings.PLAYER_SPEED_ON_THE_GROUND / 10) //{ // // Pass everything to friction (friction coefficients, the normal force on the body and the direction of friction) // mass.AddFriction(mass.staticFrictionCoefficient + terrain.mass.staticFrictionCoefficient, // (gravityNormalComponent * mass.mass), // velocityTangentialComponent); //} //else { // Pass everything to friction (friction coefficients, the normal force on the body and the direction of friction) mass.AddFriction(mass.dynamicFrictionCoefficient + terrain.mass.dynamicFrictionCoefficient, (gravityNormalComponent * mass.mass), velocityTangentialComponent); } } else { mass.AddForce(mass.mass * velocityTangentialComponent / timeInterval); } } mass.lastMaxHeight = 0; } else { //mass.AddForce((heightDifference * terrain.playerSurfaceTension * surfaceNormal) - // (new Vector3(0.0f, totalForce.Y, 0.0f) + // new Vector3(0.0f, velocity.Y * mass.mass / timeInterval, 0.0f))); // Ground pushing up to negate gravity mass.AddForce(new Vector3(0.0f, heightDifference * terrain.propSurfaceTension, 0.0f) - (new Vector3(0.0f, totalForce.Y, 0.0f) + new Vector3(0.0f, velocity.Y * mass.mass / timeInterval, 0.0f))); } // Calculate the components of our velocity with reference to the surface normal //CalculateVectorComponents(surfaceNormal, mass.velocity); } } return heightDifference; } public void CalculateVectorComponents(Vector3 surfaceNormal, Vector3 objectVelocity) { float vectorToNormalAngle, vectorToSurfaceAngle; float normalComponentOfVector; if (objectVelocity.Length() >= 0.01f || !float.IsInfinity(objectVelocity.Length())) { // Based on vector dot product to get the angle between the two vectors vectorToNormalAngle = (float)Math.Acos(Vector3.Dot(surfaceNormal, objectVelocity) / objectVelocity.Length()); // Stupid check required due to XNA's inaccuracy if (float.IsNaN(vectorToNormalAngle)) { vectorToNormalAngle = 0.0f; } // The angle between the ground and the vector is pi/2 - previous angle vectorToSurfaceAngle = (float)Math.PI / 2.0f - vectorToNormalAngle; // Scalar component of the vector in the the normal direction normalComponentOfVector = objectVelocity.Length() * (float)Math.Sin(vectorToSurfaceAngle); if (float.IsNaN(normalComponentOfVector)) { //if objectVelocity.Length gives an invalid value normalComponentOfVector = 0.0f; } // Above scalar component multiplied by the surface normal gives us the normal component of the vector normalComponent = normalComponentOfVector * surfaceNormal; // The vector plus the negative normalComponent will give us the tangential component in the reverse direction tangentialComponent = -(objectVelocity + (-normalComponent)); } else { // Object velocity is equal to zero so set both components to zero normalComponent = Vector3.Zero; tangentialComponent = Vector3.Zero; } } } }