#region File Description //----------------------------------------------------------------------------- // CollisionContext.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using RobotGameData.GameObject; using RobotGameData.Helper; using RobotGameData.GameInterface; #endregion namespace RobotGameData.Collision { #region CollisionResult public enum ResultType { /// /// To find the nearest collision from itself. /// NearestOne = 0, } /// /// result report of collision /// public class CollisionResult { /// /// Distance between detected collision /// public float distance = 0.0f; /// /// Detection object count /// public int collideCount = 0; /// /// Detected object element /// public CollideElement detectedCollide = null; /// /// intersect point /// public Vector3? intersect = null; /// /// intersect normal /// public Vector3? normal = null; public void CopyTo(ref CollisionResult target) { target.distance = this.distance; target.collideCount = this.collideCount; target.detectedCollide = this.detectedCollide; target.intersect = this.intersect; target.normal = this.normal; } public void Clear() { this.distance = 0.0f; this.collideCount = 0; this.detectedCollide = null; this.intersect = null; this.normal = null; } } #endregion #region CollisionLayer /// /// This layer groups collision elements that need to be processed /// for the collision collectively. /// public class CollisionLayer : INamed, IIdentity { #region Field string name = String.Empty; int id = -1; List collideContainer = new List(); #endregion #region Properties public string Name { get { return name; } set { name = value; } } public int Id { get { return id; } set { id = value; } } public int CollideCount { get { return collideContainer.Count; } } #endregion /// /// Add a collsion element. /// public void AddCollide(CollideElement collide) { if (collideContainer.Contains(collide)) throw new InvalidOperationException("Already entry the collide"); collideContainer.Add(collide); collide.ParentLayer = this; } /// /// Get collision element using the index. /// public CollideElement GetCollide(int index) { return collideContainer[index]; } /// /// Find a collision element using the name. /// public CollideElement FindCollide(string name) { // Finding collision by name for (int i = 0; i < collideContainer.Count; i++) { CollideElement collide = collideContainer[i]; if (collide.Name == name) return collide; } return null; } /// /// it checks whether the collision element which has been included. /// public bool IsContain(CollideElement collide) { return (collideContainer.IndexOf(collide) == -1 ? false : true); } /// /// Remove the collision element. /// public bool RemoveCollide(CollideElement collide) { return collideContainer.Remove(collide); } /// /// Remove a collision element using the index. /// public void RemoveCollide(int index) { collideContainer.RemoveAt(index); } /// /// Remove all collision elements. /// public void RemoveAll() { collideContainer.Clear(); } /// /// Make an Identity number. /// public int MakeId() { this.id = GetHashCode(); return this.id; } } #endregion /// /// It tests for collision again the registered collision elements. /// When you request CollisionContext a collision test with the source /// CollideElement, a result from a collision test would be returned with /// all CollideElements that have been registered to the specific collision /// layer as the target. /// It supports the following collision types: ray, model, box, and sphere. /// public class CollisionContext { #region Fields /// /// If set to false, all of the related functions get turned off. /// bool activeOn = true; List collideLayerContainer = new List(); CollisionResult tempResult = new CollisionResult(); int totalCollidingCount = 0; #endregion #region Properties public int LayerCount { get { return collideLayerContainer.Count; } } public int TotalCollidingCount { get { return totalCollidingCount; } } #endregion /// /// Creates a new collision layer using the name. /// /// The layer name public CollisionLayer AddLayer(string name) { CollisionLayer newLayer = new CollisionLayer(); newLayer.Name = name; newLayer.MakeId(); collideLayerContainer.Add(newLayer); return newLayer; } /// /// Get a collison layer using the ID number. /// /// ID number public CollisionLayer GetLayer(int id) { for (int i = 0; i < collideLayerContainer.Count; i++) { if( id == collideLayerContainer[i].Id) return collideLayerContainer[i]; } return null; } /// /// Get a collison layer using the name. /// /// The layer name public CollisionLayer GetLayer(string layerName) { for (int i = 0; i < collideLayerContainer.Count; i++) { if (layerName == collideLayerContainer[i].Name) return collideLayerContainer[i]; } return null; } /// /// Remove all collsion layers. /// public void ClearAllLayer() { collideLayerContainer.Clear(); } /// /// It tests for collision among the collision elements which /// have been registered to the collision layer and returns the result. /// /// Source collsion element /// Destination collison layer ID number /// type of result /// A result report public CollisionResult HitTest(CollideElement collide, int idLayer, ResultType resultType) { // Get the collide layer CollisionLayer layer = GetLayer(idLayer); return HitTest(collide, ref layer, resultType); } /// /// It tests for collision among the collision elements which /// have been registered to the collision layer and returns the result. /// /// Source collsion element /// Target collison layer /// type of result /// A result report public CollisionResult HitTest(CollideElement collide, ref CollisionLayer targetLayer, ResultType resultType) { CollisionResult result = null; tempResult.Clear(); totalCollidingCount = 0; if (activeOn == false) return null; if (collide == null) { throw new ArgumentNullException("collide"); } if (targetLayer == null) { throw new ArgumentNullException("targetLayer"); } // checking all collisions in current collision layer for (int i = 0; i < targetLayer.CollideCount; i++) { CollideElement targetCollide = targetLayer.GetCollide(i); // Skip ifself if (collide.Equals(targetCollide)) { continue; } else if (collide.Id != 0 && targetCollide.Id != 0) { if (collide.Id == targetCollide.Id) continue; } // If source collision is BoundingSphere if (collide is CollideSphere) { CollideSphere sourceCollideSphere = collide as CollideSphere; // Test with target sphere if (targetCollide is CollideSphere) { CollideSphere targetCollideSphere = targetCollide as CollideSphere; TestSphereintersectSphere(sourceCollideSphere, targetCollideSphere, ref tempResult); } // Test with target model else if (targetCollide is CollideModel) { CollideModel targetCollideModel = targetCollide as CollideModel; TestSphereintersectModel(sourceCollideSphere, targetCollideModel, ref tempResult); } // Test with target box else if (targetCollide is CollideBox) { CollideBox targetCollideBox = targetCollide as CollideBox; TestSphereintersectBox(sourceCollideSphere, targetCollideBox, ref tempResult); } // Test with target ray if (targetCollide is CollideRay) { CollideRay targetCollideRay = targetCollide as CollideRay; TestRayintersectSphere(targetCollideRay, sourceCollideSphere, ref tempResult); } } // If source collision is Ray else if (collide is CollideRay) { CollideRay sourceCollideRay = collide as CollideRay; // Test with target model if (targetCollide is CollideModel) { CollideModel targetCollideModel = targetCollide as CollideModel; TestRayintersectModel(sourceCollideRay, targetCollideModel, ref tempResult); } // Test with target sphere else if (targetCollide is CollideSphere) { CollideSphere targetCollideSphere = targetCollide as CollideSphere; TestRayintersectSphere(sourceCollideRay, targetCollideSphere, ref tempResult); } // Test with target box else if (targetCollide is CollideBox) { CollideBox targetCollideBox = targetCollide as CollideBox; TestRayintersectBox(sourceCollideRay, targetCollideBox, ref tempResult); } } // If source collision is Ray else if (collide is CollideBox) { CollideBox sourceCollideBox = collide as CollideBox; // Test with target sphere if (targetCollide is CollideSphere) { CollideSphere targetCollideSphere = targetCollide as CollideSphere; TestSphereintersectBox(targetCollideSphere, sourceCollideBox, ref tempResult); } // Test with target box else if (targetCollide is CollideBox) { CollideBox targetCollideBox = targetCollide as CollideBox; TestBoxintersectBox(sourceCollideBox, targetCollideBox, ref tempResult); } // Test with target ray else if (targetCollide is CollideRay) { CollideRay targetCollideRay = targetCollide as CollideRay; TestRayintersectBox(targetCollideRay, sourceCollideBox, ref tempResult); } } // To find the nearest detected collision. if (resultType == ResultType.NearestOne) { if (tempResult.collideCount > 0) { if(result == null) { result = new CollisionResult(); result.distance = float.MaxValue; } if (result.distance > tempResult.distance) { tempResult.CopyTo(ref result); } } } } return result; } /// /// It checks for the collision between two collision spheres. /// /// Source collision sphere /// Target collision sphere /// A result report /// True if there is a collision public bool TestSphereintersectSphere(CollideSphere sourceCollide, CollideSphere targetCollide, ref CollisionResult result) { totalCollidingCount++; // Test sphere with the other sphere if (sourceCollide.BoundingSphere.Intersects(targetCollide.BoundingSphere)) { if (result != null) { float twoSphereDistance = Vector3.Distance( targetCollide.BoundingSphere.Center, sourceCollide.BoundingSphere.Center); Vector3 twoSphereDirection = Vector3.Normalize( targetCollide.BoundingSphere.Center - sourceCollide.BoundingSphere.Center); result.distance = Math.Abs(twoSphereDistance) - (sourceCollide.Radius + targetCollide.Radius); result.detectedCollide = targetCollide; result.intersect = twoSphereDirection * result.distance; result.collideCount++; } return true; } return false; } /// /// It checks for the collision between a collision sphere and a collision model. /// /// Source collision sphere /// Target collision model /// A result report /// True if there is a collision public bool TestSphereintersectModel(CollideSphere sourceCollide, CollideModel targetCollide, ref CollisionResult result) { Vector3 intersect; Vector3 normal; float distance; BoundingSphere sphere = sourceCollide.BoundingSphere; // use quad tree. if (targetCollide.QuadTree != null) { if( TestUsingQuadTree((CollideElement)sourceCollide, targetCollide.QuadTree.RootNode, out intersect, out normal, out distance)) { result.detectedCollide = targetCollide; result.intersect = intersect; result.normal = normal; result.distance = distance; result.collideCount++; return true; } } // Hit test sphere with the model else { if( TestSphereintersectModel(sphere, targetCollide.Vertices, targetCollide.TransformMatrix, out intersect, out normal, out distance)) { result.detectedCollide = targetCollide; result.intersect = intersect; result.normal = normal; result.distance = distance; result.collideCount++; return true; } } return false; } /// /// It checks for the collision between a collision ray and a collision model. /// /// Source collision ray /// Target collision model /// A result report /// True if there is a collision public bool TestRayintersectModel(CollideRay sourceCollide, CollideModel targetCollide, ref CollisionResult result) { Vector3 intersect; Vector3 normal; float distance; // use quad tree. if (targetCollide.QuadTree != null) { if( TestUsingQuadTree((CollideElement)sourceCollide, targetCollide.QuadTree.RootNode, out intersect, out normal, out distance)) { result.detectedCollide = targetCollide; result.intersect = intersect; result.normal = normal; result.distance = distance; result.collideCount++; return true; } } // Test ray with the model else { if( TestRayintersectModel(sourceCollide.Ray, targetCollide.Vertices, targetCollide.TransformMatrix, out intersect, out normal, out distance)) { result.distance = distance; result.detectedCollide = targetCollide; result.intersect = intersect; result.normal = normal; result.collideCount++; } } return false; } /// /// It checks for the collision between a collision ray and a collision box. /// /// Source collision ray /// Target collision box /// A result report /// True if there is a collision public bool TestRayintersectBox(CollideRay sourceCollide, CollideBox targetCollide, ref CollisionResult result) { totalCollidingCount++; // Test ray with the box float? distance = sourceCollide.Ray.Intersects(targetCollide.BoundingBox); if (distance != null) { if (result != null) { result.distance = (float)distance; result.detectedCollide = targetCollide; result.intersect = null; result.normal = null; result.collideCount++; } return true; } return false; } /// /// It checks for the collision between a collision sphere and a collision box. /// /// Source collision ray /// Target collision box /// A result report /// True if there is a collision public bool TestSphereintersectBox(CollideSphere sourceCollide, CollideBox targetCollide, ref CollisionResult result) { totalCollidingCount++; // Test sphere with the box if (sourceCollide.BoundingSphere.Intersects(targetCollide.BoundingBox)) { if (result != null) { Vector3 centerBox = 0.5f * (targetCollide.BoundingBox.Max + targetCollide.BoundingBox.Min); result.distance = (float)Vector3.Distance( sourceCollide.BoundingSphere.Center, centerBox) - sourceCollide.BoundingSphere.Radius; result.detectedCollide = targetCollide; result.intersect = null; result.normal = null; result.collideCount++; } return true; } return false; } /// /// It checks for two collision boxes. /// /// Source collision box /// Target collision box /// A result report /// True if there is a collision public bool TestBoxintersectBox(CollideBox sourceCollide, CollideBox targetCollide, ref CollisionResult result) { totalCollidingCount++; // // Test two boxes if (sourceCollide.BoundingBox.Intersects(targetCollide.BoundingBox)) { if (result != null) { Vector3 centerSourceBox = 0.5f * (sourceCollide.BoundingBox.Max + sourceCollide.BoundingBox.Min); Vector3 centerTargetBox = 0.5f * (targetCollide.BoundingBox.Max + targetCollide.BoundingBox.Min); result.distance = (float)Vector3.Distance(centerSourceBox, centerTargetBox); result.detectedCollide = targetCollide; result.intersect = null; result.normal = null; result.collideCount++; } return true; } return false; } /// /// It checks for the collision between a collision ray and a collision sphere. /// /// Source collision ray /// Target collision sphere /// A result report /// True if there is a collision public bool TestRayintersectSphere(CollideRay sourceCollide, CollideSphere targetCollide, ref CollisionResult result) { totalCollidingCount++; // Test ray with the sphere float? distance = sourceCollide.Ray.Intersects(targetCollide.BoundingSphere); if (distance != null) { if (result != null) { Vector3 dir = Vector3.Normalize(sourceCollide.Ray.Position - targetCollide.BoundingSphere.Center); Vector3 length = dir * targetCollide.Radius; result.distance = (float)distance; result.detectedCollide = targetCollide; result.intersect = targetCollide.BoundingSphere.Center + length; result.normal = null; result.collideCount++; } return true; } return false; } /// /// It checks for the collision between a collision sphere and a collision model. /// protected bool TestSphereintersectModel(BoundingSphere sphere, Vector3[] vertices, Matrix transform, out Vector3 intersect, out Vector3 normal, out float distance) { distance = 0.0f; intersect = Vector3.Zero; normal = Vector3.Zero; for (int i = 0; i < vertices.Length; i += 3) { // Transform the three vertex positions into world space Vector3 v1 = Vector3.Transform(vertices[i], transform); Vector3 v2 = Vector3.Transform(vertices[i + 1], transform); Vector3 v3 = Vector3.Transform(vertices[i + 2], transform); totalCollidingCount++; // Check collision if (Helper3D.SphereIntersectTriangle(sphere.Center, sphere.Radius, v1, v2, v3, out intersect, out distance)) { normal = Vector3.Normalize(Vector3.Cross(v3 - v1, v2 - v1)); return true; } } return false; } /// /// It checks for the collision between a collision ray and a collision model. /// protected bool TestRayintersectModel(Ray ray, Vector3[] vertices, Matrix transform, out Vector3 intersect, out Vector3 normal, out float distance) { Triangle outTriangle = new Triangle(Vector3.Zero, Vector3.Zero, Vector3.Zero); distance = 0.0f; intersect = Vector3.Zero; normal = Vector3.Zero; totalCollidingCount += vertices.Length; // Test ray with the model float? checkDistance = Helper3D.RayIntersectTriangle(ray, vertices, transform, out outTriangle); if (checkDistance != null) { // Retry test for intersect point and normal return Helper3D.PointIntersect(ray.Position, ray.Direction, outTriangle, out distance, out intersect, out normal); } return false; } protected bool TestUsingQuadTree(CollideElement sourceCollide, QuadNode quadNode, out Vector3 intersect, out Vector3 normal, out float distance) { bool result = false; float tempDistance = 0.0f; Vector3 tempIntersect = Vector3.Zero; Vector3 tempNormal = Vector3.Zero; float closestDistance = float.MaxValue; Vector3 closestIntersection = Vector3.Zero; Vector3 closestNormal = Vector3.Zero; distance = 0.0f; intersect = Vector3.Zero; normal = Vector3.Zero; // checks upper left node. if (quadNode.UpperLeftNode != null) { if (TestUsingQuadTree(sourceCollide, quadNode.UpperLeftNode, out tempIntersect, out tempNormal, out tempDistance)) { result = true; // checks closest if (closestDistance > tempDistance) { closestDistance = tempDistance; closestIntersection = tempIntersect; closestNormal = tempNormal; } } } // checks upper right node. if (quadNode.UpperRightNode != null) { if (TestUsingQuadTree(sourceCollide, quadNode.UpperRightNode, out tempIntersect, out tempNormal, out tempDistance)) { result = true; // checks closest if (closestDistance > tempDistance) { closestDistance = tempDistance; closestIntersection = tempIntersect; closestNormal = tempNormal; } } } // checks lower left node. if (quadNode.LowerLeftNode != null) { if (TestUsingQuadTree(sourceCollide, quadNode.LowerLeftNode, out tempIntersect, out tempNormal, out tempDistance)) { result = true; // checks closest if (closestDistance > tempDistance) { closestDistance = tempDistance; closestIntersection = tempIntersect; closestNormal = tempNormal; } } } // checks lower right node. if (quadNode.LowerRightNode != null) { if (TestUsingQuadTree(sourceCollide, quadNode.LowerRightNode, out tempIntersect, out tempNormal, out tempDistance)) { result = true; // checks closest if (closestDistance > tempDistance) { closestDistance = tempDistance; closestIntersection = tempIntersect; closestNormal = tempNormal; } } } // checks vertices in quad node. if (quadNode.Contains(ref sourceCollide)) { // checks vertices with bounding sphere. if (sourceCollide is CollideSphere) { CollideSphere collide = sourceCollide as CollideSphere; // Hit test sphere with the model BoundingSphere sphere = collide.BoundingSphere; if (quadNode.Vertices != null) { if (TestSphereintersectModel(sphere, quadNode.Vertices, Matrix.Identity, out tempIntersect, out tempNormal, out tempDistance)) { result = true; // checks closest if (closestDistance > tempDistance) { closestDistance = tempDistance; closestIntersection = tempIntersect; closestNormal = tempNormal; } } } } // checks vertices with ray. else if (sourceCollide is CollideRay) { CollideRay collide = sourceCollide as CollideRay; if (quadNode.Vertices != null) { if (TestRayintersectModel(collide.Ray, quadNode.Vertices, Matrix.Identity, out tempIntersect, out tempNormal, out tempDistance)) { result = true; // checks closest if (closestDistance > tempDistance) { closestDistance = tempDistance; closestIntersection = tempIntersect; closestNormal = tempNormal; } } } } } // resolve final result. if (result) { distance = closestDistance; intersect = closestIntersection; normal = closestNormal; } return result; } } }