/// <summary> /// Add asteroid /// </summary> /// <param name="pos">Position</param> /// <param name="size">Size</param> /// <param name="objectSize">Object size</param> /// <param name="movement">Movement</param> private void AddAsteroid(Vector3 pos, float size, float objectSize, Vector3 movement) { Asteroid asteroid = new Asteroid(0, pos);//, objectSize); asteroid.movement = movement; asteroid.size = size; asteroid.collisionRadius = size * Asteroid.CollisionSizeCorrectionFactor; //obs: asteroid.renderSize = size * objectSize; asteroidManager.sectorAsteroids[ MiddleSector, MiddleSector].Add(asteroid); }
/// <summary> /// Handle asteroid collision, called from HandleSectorPhysics. /// </summary> /// <param name="asteroid">Asteroid</param> /// <param name="otherAsteroid">Other asteroid</param> protected void HandleAsteroidCollision( Asteroid asteroid, Asteroid otherAsteroid) { float maxAllowedDistance = otherAsteroid.collisionRadius + asteroid.collisionRadius; // Calculate distance and max. allowed distance float distance = (otherAsteroid.position - asteroid.position).Length(); // Collision detected, correct both asteroid positions first. // Middle is collision point (aprox. because we just use spheres) Vector3 middle = otherAsteroid.position * (asteroid.collisionRadius / maxAllowedDistance) + asteroid.position * (otherAsteroid.collisionRadius / maxAllowedDistance); // Calculate current relative positions to middle Vector3 positionRel = asteroid.position - middle; positionRel.Normalize(); Vector3 otherPositionRel = otherAsteroid.position - middle; otherPositionRel.Normalize(); // Put both circles outside of the collision // Add 1% to add a little distance between collided objects! otherAsteroid.position = middle + otherPositionRel * otherAsteroid.collisionRadius * 1.015f; asteroid.position = middle + positionRel * asteroid.collisionRadius * 1.015f; float asteroidSpeed = asteroid.movement.Length(); float otherAsteroidSpeed = otherAsteroid.movement.Length(); // Mass = size*size (size*size*size is too much) float asteroidMass = asteroid.size * asteroid.size;// *asteroid.size; float otherAsteroidMass = otherAsteroid.size * otherAsteroid.size;// *otherAsteroid.size; float bothMasses = asteroidMass + otherAsteroidMass; // Calculate force with speed * mass (mass=size) float asteroidForce = asteroidSpeed * asteroidMass; float otherAsteroidForce = otherAsteroidSpeed * otherAsteroidMass; float bothForces = asteroidForce + otherAsteroidForce; // Copy over normals Vector3 asteroidNormal = positionRel; Vector3 otherAsteroidNormal = otherPositionRel; // Normalize movement Vector3 asteroidDirection = asteroid.movement; // Update for Xna: Make sure we always got valid values, // (0, 0, 0).Normalize will produce NaN! if (asteroidDirection != Vector3.Zero) asteroidDirection.Normalize(); Vector3 otherAsteroidDirection = otherAsteroid.movement; if (otherAsteroidDirection != Vector3.Zero) otherAsteroidDirection.Normalize(); // Get collision strenth (1 if pointing in same direction, // 0 if 90 degrees) for both asteroids. float asteroidCollisionStrength = Math.Abs(Vector3.Dot( asteroidDirection, asteroidNormal)); float otherAsteroidCollisionStrength = Math.Abs(Vector3.Dot( otherAsteroidDirection, otherAsteroidNormal)); Vector3 asteroidReflection = ReflectVector(asteroidDirection, asteroidNormal); Vector3 otherAsteroidReflection = ReflectVector(otherAsteroidDirection, otherAsteroidNormal); // Make sure the strength is calculated correctly // We have also to correct the reflection vector if the length was 0, // use the normal vector instead. if (asteroidDirection.Length() <= 0.01f) { asteroidCollisionStrength = otherAsteroidCollisionStrength; asteroidReflection = asteroidNormal; } // if (asteroidDirection.Length) if (otherAsteroidDirection.Length() <= 0.01f) { otherAsteroidCollisionStrength = asteroidCollisionStrength; otherAsteroidReflection = otherAsteroidNormal; } // if (otherAsteroidDirection.Length) // Ok, now the complicated part, everything above was really easy! asteroid.movement = asteroidReflection * // So, first we have to reflect our current movement speed. // This will be scaled to 1-strength to only account the reflection // amount (imagine a collision with a static wall). In most cases // Strength is close to 1 and this reflection will be very small. ((1 - asteroidCollisionStrength) * asteroidSpeed + // And finally we have to add the impuls, which is calculated // by the formular ((m1-m2)*v1 + 2*m2*v2)/(m1+m2), see // http://de.wikipedia.org/wiki/Sto%C3%9F_%28Physik%29 for more help. (asteroidCollisionStrength * (Math.Abs(asteroidMass - otherAsteroidMass) * asteroidSpeed + (2 * otherAsteroidMass * otherAsteroidSpeed)) / bothMasses)); // Same for other asteroid, just with asteroid and otherAsteroid // inverted. otherAsteroid.movement = otherAsteroidReflection * // Same as above. ((1 - otherAsteroidCollisionStrength) * otherAsteroidSpeed + (otherAsteroidCollisionStrength * (Math.Abs(otherAsteroidMass - asteroidMass) * otherAsteroidSpeed + (2 * asteroidMass * asteroidSpeed)) / bothMasses)); asteroid.rotationSpeed.Y = (asteroid.rotationSpeed.Y + RandomHelper.GetRandomFloat(-0.75f, +1.0f)) / 2.0f; otherAsteroid.rotationSpeed.Y = (otherAsteroid.rotationSpeed.Y + RandomHelper.GetRandomFloat(-0.75f, +1.0f)) / 2.0f; }
} // HandleSectorPhysics(checkX, checkY, checkZ) /// <summary> /// Handle asteroid collision, called from HandleSectorPhysics. /// </summary> /// <param name="asteroid">Asteroid</param> /// <param name="otherAsteroid">Other asteroid</param> protected void HandleAsteroidCollision( Asteroid asteroid, Asteroid otherAsteroid) { float maxAllowedDistance = otherAsteroid.collisionRadius + asteroid.collisionRadius; // Calculate distance and max. allowed distance float distance = (otherAsteroid.position - asteroid.position).Length(); // Collision detected, correct both asteroid positions first. // Middle is collision point (aprox. because we just use spheres) Vector3 middle = otherAsteroid.position * (asteroid.collisionRadius / maxAllowedDistance) + asteroid.position * (otherAsteroid.collisionRadius / maxAllowedDistance); // Calculate current relative positions to middle Vector3 positionRel = asteroid.position - middle; positionRel.Normalize(); Vector3 otherPositionRel = otherAsteroid.position - middle; otherPositionRel.Normalize(); // Put both circles outside of the collision // Add 1% to add a little distance between collided objects! otherAsteroid.position = middle + otherPositionRel * otherAsteroid.collisionRadius * 1.015f; asteroid.position = middle + positionRel * asteroid.collisionRadius * 1.015f; float asteroidSpeed = asteroid.movement.Length(); float otherAsteroidSpeed = otherAsteroid.movement.Length(); // Mass = size*size (size*size*size is too much) float asteroidMass = asteroid.size * asteroid.size; // *asteroid.size; float otherAsteroidMass = otherAsteroid.size * otherAsteroid.size; // *otherAsteroid.size; float bothMasses = asteroidMass + otherAsteroidMass; // Calculate force with speed * mass (mass=size) float asteroidForce = asteroidSpeed * asteroidMass; float otherAsteroidForce = otherAsteroidSpeed * otherAsteroidMass; float bothForces = asteroidForce + otherAsteroidForce; // Copy over normals Vector3 asteroidNormal = positionRel; Vector3 otherAsteroidNormal = otherPositionRel; // Normalize movement Vector3 asteroidDirection = asteroid.movement; // Update for Xna: Make sure we always got valid values, // (0, 0, 0).Normalize will produce NaN! if (asteroidDirection != Vector3.Zero) { asteroidDirection.Normalize(); } Vector3 otherAsteroidDirection = otherAsteroid.movement; if (otherAsteroidDirection != Vector3.Zero) { otherAsteroidDirection.Normalize(); } // Get collision strenth (1 if pointing in same direction, // 0 if 90 degrees) for both asteroids. float asteroidCollisionStrength = Math.Abs(Vector3.Dot( asteroidDirection, asteroidNormal)); float otherAsteroidCollisionStrength = Math.Abs(Vector3.Dot( otherAsteroidDirection, otherAsteroidNormal)); Vector3 asteroidReflection = ReflectVector(asteroidDirection, asteroidNormal); Vector3 otherAsteroidReflection = ReflectVector(otherAsteroidDirection, otherAsteroidNormal); // Make sure the strength is calculated correctly // We have also to correct the reflection vector if the length was 0, // use the normal vector instead. if (asteroidDirection.Length() <= 0.01f) { asteroidCollisionStrength = otherAsteroidCollisionStrength; asteroidReflection = asteroidNormal; } // if (asteroidDirection.Length) if (otherAsteroidDirection.Length() <= 0.01f) { otherAsteroidCollisionStrength = asteroidCollisionStrength; otherAsteroidReflection = otherAsteroidNormal; } // if (otherAsteroidDirection.Length) // Ok, now the complicated part, everything above was really easy! asteroid.movement = asteroidReflection * // So, first we have to reflect our current movement speed. // This will be scaled to 1-strength to only account the reflection // amount (imagine a collision with a static wall). In most cases // Strength is close to 1 and this reflection will be very small. ((1 - asteroidCollisionStrength) * asteroidSpeed + // And finally we have to add the impuls, which is calculated // by the formular ((m1-m2)*v1 + 2*m2*v2)/(m1+m2), see // http://de.wikipedia.org/wiki/Sto%C3%9F_%28Physik%29 for more help. (asteroidCollisionStrength * (Math.Abs(asteroidMass - otherAsteroidMass) * asteroidSpeed + (2 * otherAsteroidMass * otherAsteroidSpeed)) / bothMasses)); // Same for other asteroid, just with asteroid and otherAsteroid // inverted. otherAsteroid.movement = otherAsteroidReflection * // Same as above. ((1 - otherAsteroidCollisionStrength) * otherAsteroidSpeed + (otherAsteroidCollisionStrength * (Math.Abs(otherAsteroidMass - asteroidMass) * otherAsteroidSpeed + (2 * asteroidMass * asteroidSpeed)) / bothMasses)); asteroid.rotationSpeed.Y = (asteroid.rotationSpeed.Y + RandomHelper.GetRandomFloat(-0.75f, +1.0f)) / 2.0f; otherAsteroid.rotationSpeed.Y = (otherAsteroid.rotationSpeed.Y + RandomHelper.GetRandomFloat(-0.75f, +1.0f)) / 2.0f; } // HandleAsteroidCollision(asteroid, otherAsteroid)
} // PhysicsAsteroidManager(setLevel) #endregion #region Player asteroid collision /// <summary> /// Player asteroid collision, used to check if player collided with /// any asteroid nearby. Returns a value between 0 and 1, 0 means /// no collision has occurred and 1.0 means we have done a frontal crash /// and will lose a lot of hitpoints. Side hits do hurt do, but are not /// as fatal. This method will also play the side hit and whosh sounds. /// </summary> /// <returns>0 if no collision happened, 1 for a frontal collision. /// Everything in between is just some strafe collision (doesn't kill us). /// </returns> public float PlayerAsteroidCollision() { try { Vector3 camPos = BaseGame.CameraPos; Vector3 camDir = new Vector3(0, 0, -1); camDir = Vector3.TransformNormal(camDir, BaseGame.ViewMatrix); Vector3 nextCamPos1 = camPos + camDir * 10.0f; Vector3 nextCamPos2 = camPos + camDir * (10.0f + Player.Speed * BaseGame.MoveFactorPerSecond * Player.MovementSpeedPerSecond); float damageFactor = 0.0f; float remToPlayWhoshVolume = 0.0f; // Helpers for intersection test Vector3 v = new Vector3(0, 0, 1); Matrix m = BaseGame.InverseViewMatrix; // Transform the screen space pick ray into 3D space Vector3 rayDir = new Vector3( v.X * m.M11 + v.Y * m.M21 + v.Z * m.M31, v.X * m.M12 + v.Y * m.M22 + v.Z * m.M32, v.X * m.M13 + v.Y * m.M23 + v.Z * m.M33); Vector3 rayOrigin = new Vector3( m.M41, m.M42, m.M43); for (int z = -1; z <= +1; z++) { //for (int y = -1; y <= +1; y++) for (int x = -1; x <= +1; x++) { List <Asteroid> thisSector = sectorAsteroids[ MiddleSector + z, MiddleSector + x]; for (int num = 0; num < thisSector.Count; num++) { Asteroid asteroid = thisSector[num]; // Check distance to camera float distance = (asteroid.position - camPos).Length(); // If distance is smaller than collision radius, we could have // a hit. Now do polygon based collision testing. if (distance <= asteroid.size * 0.825157789f) // * 1.25f) { // First of all do an intersection test to see if we // really hit something here. Matrix invWorldMatrix = Matrix.Invert(asteroid.RenderMatrix); Vector3 rayWorldPos = Vector3.Transform(rayOrigin, invWorldMatrix); Vector3 rayWorldDir = Vector3.TransformNormal(rayDir, invWorldMatrix); rayWorldDir.Normalize(); /*obs, not supported in XNA * // Use D3DMesh.Intersect's capability to determinate if * // our rocket (represented as a ray) hits the asteroid mesh. * IntersectInformation closestHit; * if (asteroidModels[asteroid.Type].D3DMesh.Intersect( * rayWorldPos, rayWorldDir, out closestHit)) * { * // Check if we hit near the camera, else discard. * // Use speed to see how much we can still move to avoid * // collision! * if (closestHit.Dist > 15.0f + Player.Speed * * Player.MovementSpeedPerSecond * * BaseGame.MoveFactorPerSecond * 2.0f) * { * // To far away, skip this collision! * continue; * } // if (closestHit.Dist) * } // if (asteroidModels[asteroid.Type].D3DMesh.Intersect) * else * { * // Is this donut asteroid? Then check if we just fly * // through it. * if (asteroid.Type == 4 && * asteroid.movement.LengthSq() > 0 && * distance <= 25.0f + Player.Speed * * Player.MovementSpeedPerSecond * * BaseGame.MoveFactorPerSecond * 1.25f) * { * // Give player additional 4000 points, * // thats really a lot! * Player.score += 4000; * // Add text message * Player.SetItemMessage(Texts.FlyThroughBonus); * // Mark this one as already handled, don't add score * // anymore. * asteroid.movement = Vector3.Zero; * } // if (asteroid.Type) * * // No real collision here, just in bounding sphere * continue; * } // else */ // Find out how bad it is, add 25% to make collision hit // hurt much more than just flying by very closely. float thisDamageFactor = // Use fixed value, always hurt player the same way, // We just can't say how the player collided without poly checking 0.175f + 0.25f * Math.Max(0, 1.0f - (distance / (asteroid.collisionRadius * 1.025f))); // Big asteroids to more damage than smaller ones thisDamageFactor *= 0.5f + 0.5f * (asteroid.size / MaxAsteroidSize); // Check highest damage factor if (thisDamageFactor > damageFactor) { damageFactor = thisDamageFactor; } // Play side hit sound. // Play only once (asteroid is killed after that). Sound.Play(Sound.Sounds.SideHit); // Remember to show hit direction effect // Get direction from 3d data (similar to sound // calculation below). Vector3 distVector = asteroid.position - camPos; distVector = Vector3.TransformNormal( distVector, BaseGame.ViewMatrix); SetHitDirectionEffect( (float)Math.Atan2(distVector.X, distVector.Y)); // Lets rumble a bit if we are hurt Input.GamePadRumble( 0.125f + 0.4f * damageFactor, 0.25f + 0.45f * damageFactor); //0.25f + damageFactor / 2.0f, //0.45f + 0.55f * damageFactor); // In bomb mode, we have 1 free hit if (Player.numberOfBombItems > 0) { Player.numberOfBombItems--; damageFactor = 0.0f; } // if (Player.numberOfBombItems) // Always remove asteroid we hit! // This way we hit it only once. thisSector.RemoveAt(num--); } // if (distance) float distanceSoon = (asteroid.position - nextCamPos1).Length(); // Check if some asteroids are getting really close if (distanceSoon < MaxAsteroidSize * 3.5f) { // Determinate whosh by checking if any asteroid comes nearer // than SectorDepth then check if the asteroid is not longer // closing in (but moving away from us). // Now play sound, this should only happen once per asteroid! // Find out if the asteroid is not longer moving closer if (distanceSoon < (asteroid.position - nextCamPos2).Length()) { // Play one of the whosh sounds float loudness = 1.0f - ((distanceSoon - asteroid.collisionRadius * 1.5f) / (MaxAsteroidSize * 3.5f - asteroid.collisionRadius * 1.5f)); loudness *= 1.25f; if (loudness > 1.0f) { loudness = 1.0f; } if (loudness > 0.1f) { // Lets rumble a bit to indicate flying near if (loudness > 0.8f) { Input.GamePadRumble(0.05f, 0.15f); } // Get panning from 3d data Vector3 distVector = asteroid.position - nextCamPos1; distVector = Vector3.TransformNormal( distVector, BaseGame.ViewMatrix); float panning = distVector.X / MaxAsteroidSize * 4.0f; if (panning < -0.7f) { panning = -0.7f; } else if (panning > 0.7f) { panning = 0.7f; } // Play whosh sound float newVolume = 0.5f + 0.5f * loudness; if (newVolume > remToPlayWhoshVolume) { remToPlayWhoshVolume = newVolume; } if (distance <= asteroid.collisionRadius * 2.5f) { // Make camera wobbel (use smaller amounts) Player.SetCameraWobbel(0.33f * (1.0f - (distance / (asteroid.collisionRadius * 2.0f)))); } // if (distance) // Add a little score for flying so close ^^ if (BaseGame.TotalFrames % 20 == 0) { Player.score += (int)BaseGame.ElapsedTimeThisFrameInMs; } } // if (loudness) } // if (distanceSoon) } // if (distanceSoon) } // for (num) } // for for for (int) } // Max. check every 10 frames, else we have to many whosh sounds! if (remToPlayWhoshVolume > 0 && BaseGame.TotalFrames % 5 == 0) { Sound.PlayWhosh(remToPlayWhoshVolume); } return(damageFactor); } // try catch (Exception ex) { // Note: This happened a few times in the beta, no reason to crash // the whole game if anything simple here fails (physics or sound). Log.Write("Failed to execute PlayerAsteroidCollision: " + ex.ToString()); return(0.0f); } // catch } // PlayerAsteroidCollision()
} // KillAllInnerSectorAsteroids() #endregion #region Handle sector physics /// <summary> /// Handle sector physics. See below for more explanations and /// first simpler versions in the unit test region. /// /// Will handle all collisions inside a given sector. We will not /// check against the whole universe, which is way to slow. /// Just checking this sector is very fast, but does not cover any /// collisions at the borders (which occur VERY often, the first /// version of this method ignored the borders and sucked big time). /// For this reason we will check all neighbouring sectors too! /// Note: All asteroids should have an updated position here. /// The position will be corrected if a collision occurs and next /// frame the asteroids will move into the reflected direction. /// </summary> protected override void HandleSectorPhysics( int checkX, //int checkY, int checkZ, int cameraSectorPosX, //int cameraSectorPosY, int cameraSectorPosZ) { List <Asteroid> thisSectorAsteroids = sectorAsteroids[checkZ, checkX]; // No asteroids in this sector? Then no collision can occur. if (thisSectorAsteroids.Count == 0) { return; } // 2 modes: inside sector and at border. // If we are inside a simple inner check is totally fine. // If we are at any border of the current sector, we check // all surrounding sectors and include other asteroids in our search! // Note: I've also pfusched in the out of sector check inside this loop, // this is very ugly but saves a good amount of performance! for (int num = 0; num < thisSectorAsteroids.Count; num++) { Asteroid asteroid = thisSectorAsteroids[num]; // Get position in percentage (we use this variable several times) float xp = asteroid.position.X / SectorWidth; float zp = asteroid.position.Z / SectorDepth; // Get sector position, use asteroid pos and round it. int ix = (int)Math.Round(xp); int iz = (int)Math.Round(zp); // Calculate border distance -0.5 means left border, -0.4 to +0.4 // means we are inside the sector and +0.5 means right border. float borderX = xp - ix; float borderZ = zp - iz; // Substract current position from sector position ix = ix - cameraSectorPosX + MiddleSector; iz = iz - cameraSectorPosZ + MiddleSector; // Bounding checks if (ix < 0) { ix = 0; } if (iz < 0) { iz = 0; } if (ix >= NumberOfSectors) { ix = NumberOfSectors - 1; } if (iz >= NumberOfSectors) { iz = NumberOfSectors - 1; } // Change asteroid sector if it is not longer in the correct sector. if (ix != checkX || iz != checkZ) { // Remove from old list and reuse old num again. thisSectorAsteroids.RemoveAt(num--); // Add to new sector, which fits our current position. sectorAsteroids[iz, ix].Add(asteroid); } // if (ix) // Full loop for all possible neighbour configurations is only used // if we are at any border. In most cases we don't ever check any // neighbour sectors. If we have to, that will be a lot of crazy // code and checks to optimize every last bit out of this loops! if (borderX > -0.4f && borderX < 0.4f && borderZ > -0.4f && borderZ < 0.4f) { // Only check this sector! Crosscheck with any other asteroid in // this sector. //foreach (Asteroid otherAsteroid in thisSectorAsteroids) for (int asteroidNum = 0; asteroidNum < thisSectorAsteroids.Count; asteroidNum++) { Asteroid otherAsteroid = thisSectorAsteroids[asteroidNum]; if (asteroid != otherAsteroid) { float maxAllowedDistance = otherAsteroid.collisionRadius + asteroid.collisionRadius; // Distance smaller than max. allowed distance? if ((otherAsteroid.position - asteroid.position).LengthSquared() < maxAllowedDistance * maxAllowedDistance) { HandleAsteroidCollision(asteroid, otherAsteroid); } // if (otherAsteroid.position) } // if (asteroid) } // for } // if (borderX) else { // Check neighbour sectors too. // Go through list of asteroids in surrounding sectors. // Please also note the order we go through the multidimensional // lists, by using z, y, x we can let the cpu cache memory // accesses better. for (int z = checkZ - 1; z <= checkZ + 1; z++) { if (z >= 0 && z < NumberOfSectors) { //for (int y = checkY - 1; y <= checkY + 1; y++) // if (y >= 0 && y < NumberOfSectors) for (int x = checkX - 1; x <= checkX + 1; x++) { if (x >= 0 && x < NumberOfSectors && sectorIsVisible[z, x]) { // Check each of this other asteroids //foreach (Asteroid otherAsteroid in sectorAsteroids[z, x]) for (int otherAsteroidNum = 0; otherAsteroidNum < sectorAsteroids[z, x].Count; otherAsteroidNum++) { Asteroid otherAsteroid = sectorAsteroids[z, x][otherAsteroidNum]; if (asteroid != otherAsteroid) { float maxAllowedDistance = otherAsteroid.collisionRadius + asteroid.collisionRadius; // Distance smaller than max. allowed distance? if ((otherAsteroid.position - asteroid.position).LengthSquared() < maxAllowedDistance * maxAllowedDistance) { HandleAsteroidCollision(asteroid, otherAsteroid); } // if (otherAsteroid.position) } // if (asteroid) } // for } // for if for if for if (x) } } } } // else } // for (num) } // HandleSectorPhysics(checkX, checkY, checkZ)