/// <summary> /// Llena la información de colisión entre dos cajas, una vez se conoce que hay contacto del tipo vértice - cara /// </summary> /// <param name="one">Caja uno</param> /// <param name="two">Caja dos</param> /// <param name="toCentre">Distancia entre centros</param> /// <param name="data">Información de colisión</param> /// <param name="best">Eje de penetración menor</param> /// <param name="pen">Pentración menor</param> private static void FillPointFaceBoxBox(CollisionBox one, CollisionBox two, Vector3 toCentre, uint best, float pen, ref CollisionData data) { // Sabemos cual es el eje de la colisión, pero tenemos que conocer con qué cara tenemos que trabjar Vector3 normal = one.GetAxis((TransformAxis)best); if (Vector3.Dot(one.GetAxis((TransformAxis)best), toCentre) > 0f) { normal = normal * -1.0f; } // Obtenemos el vértice Vector3 vertex = two.HalfSize; if (Vector3.Dot(two.XAxis, normal) < 0f) { vertex.X = -vertex.X; } if (Vector3.Dot(two.YAxis, normal) < 0f) { vertex.Y = -vertex.Y; } if (Vector3.Dot(two.ZAxis, normal) < 0f) { vertex.Z = -vertex.Z; } Contact contact = data.CurrentContact; contact.ContactNormal = normal; contact.Penetration = pen; contact.ContactPoint = Vector3.Transform(vertex, two.Transform); RigidBody rbOne = one; RigidBody rbTwo = two; contact.SetBodyData(ref rbOne, ref rbTwo, data.Friction, data.Restitution); }
/// <summary> /// Ejecuta el test de intersección entre el OBB y el Triángulo /// </summary> /// <param name="box">OBB</param> /// <param name="tri">Triángulo</param> /// <param name="edge">Lado</param> /// <param name="axis">Eje a testear (0, 1 ó 2)</param> /// <returns>Devuelve verdadero si la intersección no ha sido descartada</returns> private static bool OverlapOnAxis(CollisionBox box, Triangle tri, Vector3 edge, int axis) { if (axis == 0) { // a.X ^ b.X = (1,0,0) ^ edge // axis = Vector3(0, -edge.Z, edge.Y); float dPoint1 = tri.Point1.Z * edge.Y - tri.Point1.Y * edge.Z; float dPoint2 = tri.Point2.Z * edge.Y - tri.Point2.Y * edge.Z; float dPoint3 = tri.Point3.Z * edge.Y - tri.Point3.Y * edge.Z; float dhalf = Math.Abs(box.HalfSize.Y * edge.Z) + Math.Abs(box.HalfSize.Z * edge.Y); if (Math.Min(dPoint1, Math.Min(dPoint2, dPoint3)) >= dhalf || Math.Max(dPoint1, Math.Max(dPoint2, dPoint3)) <= -dhalf) { return(true); } else { return(false); } } else if (axis == 1) { // a.Y ^ b.X = (0,1,0) ^ edge // axis = Vector3(edge.Z, 0, -edge.X); float dPoint1 = tri.Point1.X * edge.Z - tri.Point1.Z * edge.X; float dPoint2 = tri.Point2.X * edge.Z - tri.Point2.Z * edge.X; float dPoint3 = tri.Point3.X * edge.Z - tri.Point3.Z * edge.X; float dhalf = Math.Abs(box.HalfSize.X * edge.Z) + Math.Abs(box.HalfSize.Z * edge.X); if (Math.Min(dPoint1, Math.Min(dPoint2, dPoint3)) >= dhalf || Math.Max(dPoint1, Math.Max(dPoint2, dPoint3)) <= -dhalf) { return(true); } else { return(false); } } else if (axis == 2) { // a.Y ^ b.X = (0,0,1) ^ edge // axis = Vector3(-edge.Y, edge.X, 0); float dPoint1 = tri.Point1.Y * edge.X - tri.Point1.X * edge.Y; float dPoint2 = tri.Point2.Y * edge.X - tri.Point2.X * edge.Y; float dPoint3 = tri.Point3.Y * edge.X - tri.Point3.X * edge.Y; float dhalf = Math.Abs(box.HalfSize.Y * edge.X) + Math.Abs(box.HalfSize.X * edge.Y); if (Math.Min(dPoint1, Math.Min(dPoint2, dPoint3)) >= dhalf || Math.Max(dPoint1, Math.Max(dPoint2, dPoint3)) <= -dhalf) { return(true); } else { return(false); } } else { throw new Exception(); } }
/// <summary> /// Detecta la intersección entre una caja y un triángulo /// </summary> /// <param name="box">Caja</param> /// <param name="tri">Triángulo</param> /// <returns>Devuelve verdadero si hay intersección</returns> public static bool BoxAndTri(CollisionBox box, Triangle tri) { //Transformar la caja a coordenadas locales para poder usar un AABB-Triangle Quaternion orientation = box.Orientation; Quaternion orientationInv; Quaternion.Conjugate(ref orientation, out orientationInv); //Llevar el triángulo a las coordenadas de la caja Matrix minv = Matrix.CreateFromQuaternion(orientationInv); Vector3 localPoint1 = Vector3.TransformNormal(tri.Point1 - box.Position, minv); Vector3 localPoint2 = Vector3.TransformNormal(tri.Point2 - box.Position, minv); Vector3 localPoint3 = Vector3.TransformNormal(tri.Point3 - box.Position, minv); Triangle localTri = new Triangle(localPoint1, localPoint2, localPoint3); BoundingBox localTriBounds = PhysicsMathHelper.GenerateFromTriangle(localTri); Vector3 localTriBoundhalfExtent = localTriBounds.GetHalfSizes(); Vector3 localTriBoundCenter = localTriBounds.GetCenter(); float localTriBoundCenterX = Math.Abs(localTriBoundCenter.X); float localTriBoundCenterY = Math.Abs(localTriBoundCenter.Y); float localTriBoundCenterZ = Math.Abs(localTriBoundCenter.Z); if (localTriBoundhalfExtent.X + box.HalfSize.X <= localTriBoundCenterX || localTriBoundhalfExtent.Y + box.HalfSize.Y <= localTriBoundCenterY || localTriBoundhalfExtent.Z + box.HalfSize.Z <= localTriBoundCenterZ) { //El cuerpo está fuera de la caja return(false); } if (localTriBoundhalfExtent.X + localTriBoundCenterX <= box.HalfSize.X && localTriBoundhalfExtent.Y + localTriBoundCenterY <= box.HalfSize.Y && localTriBoundhalfExtent.Z + localTriBoundCenterZ <= box.HalfSize.Z) { //El cuerpo está dentro de la caja return(true); } Vector3 point1 = localTri.Point1; Vector3 point2 = localTri.Point2; Vector3 point3 = localTri.Point3; //Obtener eje 1, entre el punto 1 y el 2 del triángulo Vector3 edge1; Vector3.Subtract(ref point2, ref point1, out edge1); //Obtener eje 2, entre el punto 1 y el 3 del triángulo Vector3 edge2; Vector3.Subtract(ref point3, ref point1, out edge2); //Obtener el eje perpendicular entre los dos ejes Vector3 crossEdge; Vector3.Cross(ref edge1, ref edge2, out crossEdge); //Obtener la distancia del triángulo al eje float triangleDist = Vector3.Dot(localTri.Point1, crossEdge); if (Math.Abs(crossEdge.X * box.HalfSize.X) + Math.Abs(crossEdge.Y * box.HalfSize.Y) + Math.Abs(crossEdge.Z * box.HalfSize.Z) <= Math.Abs(triangleDist)) { return(false); } // No hay resultados en los tests con el AABB del triángulo, hay que probar los 9 casos, 3 por eje // Al usar la transformación local de la caja, cada plano calculado es paralelo a cada eje de la caja // Como son paralelos, el producto es siempre 0 y se puede omitir //Obtener eje 3, entre el punto 2 y el 3 del triángulo Vector3 edge3; Vector3.Subtract(ref point2, ref point3, out edge3); if (OverlapOnAxis(box, localTri, edge1, 0)) { return(false); } if (OverlapOnAxis(box, localTri, edge2, 0)) { return(false); } if (OverlapOnAxis(box, localTri, edge3, 0)) { return(false); } if (OverlapOnAxis(box, localTri, edge1, 1)) { return(false); } if (OverlapOnAxis(box, localTri, edge2, 1)) { return(false); } if (OverlapOnAxis(box, localTri, edge3, 1)) { return(false); } if (OverlapOnAxis(box, localTri, edge1, 2)) { return(false); } if (OverlapOnAxis(box, localTri, edge2, 2)) { return(false); } if (OverlapOnAxis(box, localTri, edge3, 2)) { return(false); } #if DEBUG if (m_DEBUGUSE) { m_DEBUGTRI[m_DEBUGTRICOUNT++] = tri; m_DEBUGAABB[m_DEBUGAABBCOUNT++] = PhysicsMathHelper.GenerateFromTriangle(tri); } #endif return(true); }
/// <summary> /// Detecta la intersección entre una caja y un plano /// </summary> /// <param name="box">Caja</param> /// <param name="plane">Plano</param> /// <returns>Devuelve verdadero si hay intersección</returns> public static bool BoxAndPlane(CollisionBox box, Plane plane) { return(BoxAndPlane(box, plane.Normal, plane.D)); }
/// <summary> /// Detecta la colisión entre una caja y una colección de triángulos /// </summary> /// <param name="box">Caja</param> /// <param name="triangleSoup">Colección de triángulos</param> /// <param name="data">Datos de colisión a llenar</param> /// <returns>Devuelve verdadero si existe colisión, falso en el resto de los casos</returns> public static bool BoxAndTriangleSoup(CollisionBox box, CollisionTriangleSoup triangleSoup, ref CollisionData data) { if (data.ContactsLeft <= 0) { // Si no hay más contactos disponibles se sale de la función. return(false); } bool intersection = false; int contacts = 0; foreach (Triangle triangle in triangleSoup.Triangles) { // Comprobar la intersección if (IntersectionTests.BoxAndTri(box, triangle)) { // Hay intersección, ahora hay que encontrar los puntos de intersección. // Podemos hacerlo únicamente chequeando los vértices. // Si la caja está descansando sobre el plano o un eje, se reportarán cuatro o dos puntos de contacto. for (int i = 0; i < 8; i++) { // Calcular la positición de cada vértice Vector3 vertexPos = box.GetCorner(i); Plane plane = triangle.Plane; // Calcular la distancia al plano float distanceToPlane = Vector3.Dot(vertexPos, plane.Normal) + plane.D; // Si la distancia es negativa está tras el plano. Si es 0, está en el plano if (distanceToPlane <= 0f) { // Intersección entre línea y triángulo Vector3 direction = vertexPos - box.Position; if (IntersectionTests.TriAndRay(triangle, new Ray(box.Position, direction))) { intersection = true; contacts++; // Crear la información del contacto. // El punto de contacto está a medio camino entre el vértice y el plano. // Se obtiene multiplicando la dirección por la mitad de la distancia de separación, y añadiendo la posición del vértice. Contact contact = data.CurrentContact; contact.ContactPoint = vertexPos; contact.ContactNormal = plane.Normal; contact.Penetration = -distanceToPlane; // Establecer los datos del contacto RigidBody one = box.Body; RigidBody two = null; contact.SetBodyData(ref one, ref two, data.Friction, data.Restitution); // Añadir contacto data.AddContact(); if (data.ContactsLeft <= 0) { return(true); } if (contacts > 4) { return(true); } } } } } } return(intersection); }
/// <summary> /// Detecta la colisión entre una caja y una esfera /// </summary> /// <param name="box">Caja</param> /// <param name="sphere">Esfera</param> /// <param name="data">Datos de colisión a llenar</param> /// <returns>Devuelve verdadero si existe colisión, falso en el resto de los casos</returns> public static bool BoxAndSphere(CollisionBox box, CollisionSphere sphere, ref CollisionData data) { if (data.ContactsLeft <= 0) { // Si no hay más contactos disponibles se sale de la función. return(false); } // Transformar el cetro de la esfera Vector3 centre = sphere.Position; Vector3 relCentre = Vector3.Transform(centre, Matrix.Invert(box.Transform)); // Comprobar si se puede excluir el contacto if (Math.Abs(relCentre.X) - sphere.Radius > box.HalfSize.X || Math.Abs(relCentre.Y) - sphere.Radius > box.HalfSize.Y || Math.Abs(relCentre.Z) - sphere.Radius > box.HalfSize.Z) { return(false); } Vector3 closestPt = Vector3.Zero; float dist = relCentre.X; if (dist > box.HalfSize.X) { dist = box.HalfSize.X; } if (dist < -box.HalfSize.X) { dist = -box.HalfSize.X; } closestPt.X = dist; dist = relCentre.Y; if (dist > box.HalfSize.Y) { dist = box.HalfSize.Y; } if (dist < -box.HalfSize.Y) { dist = -box.HalfSize.Y; } closestPt.Y = dist; dist = relCentre.Z; if (dist > box.HalfSize.Z) { dist = box.HalfSize.Z; } if (dist < -box.HalfSize.Z) { dist = -box.HalfSize.Z; } closestPt.Z = dist; // Comprobar si estamos en contacto. dist = (closestPt - relCentre).LengthSquared(); if (dist > sphere.Radius * sphere.Radius) { return(false); } Vector3 closestPtWorld = Vector3.Transform(closestPt, box.Transform); //HACKBYME: Añadimos la velocidad de la esfera para calcular la normal Vector3 relativeVelocity = sphere.Body.Velocity + box.Body.Velocity; Vector3 normal = Vector3.Normalize(closestPtWorld - centre + relativeVelocity); Contact contact = data.CurrentContact; contact.ContactNormal = normal; contact.ContactPoint = closestPtWorld; contact.Penetration = sphere.Radius - (float)Math.Sqrt(dist); contact.SetBodyData(ref box.Body, ref sphere.Body, data.Friction, data.Restitution); data.AddContact(); return(true); }
/// <summary> /// Detecta la colisión entre cajas /// </summary> /// <param name="one">Caja uno</param> /// <param name="two">Caja dos</param> /// <param name="data">Datos de colisión</param> /// <returns>Devuelve verdadero si hay colisión, o falso en el resto de los casos</returns> public static bool BoxAndBox(CollisionBox one, CollisionBox two, ref CollisionData data) { if (data.ContactsLeft <= 0) { // Si no hay más contactos disponibles se sale de la función. return(false); } // Encontrar el vector entre los dos centros Vector3 toCentre = two.Position - one.Position; // Se asume que no hay contacto float pen = float.MaxValue; uint best = uint.MaxValue; // Chequear cada eje, almacenando penetración y el mejor eje if (!TryAxis(one, two, one.XAxis, toCentre, 0, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, one.YAxis, toCentre, 1, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, one.ZAxis, toCentre, 2, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, two.XAxis, toCentre, 3, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, two.YAxis, toCentre, 4, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, two.ZAxis, toCentre, 5, ref pen, ref best)) { return(false); } // Almacenar el mejor eje hasta ahora, en el caso de estar en una colisión de ejes paralelos más adelante. uint bestSingleAxis = best; if (!TryAxis(one, two, Vector3.Cross(one.XAxis, two.XAxis), toCentre, 6, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.XAxis, two.YAxis), toCentre, 7, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.XAxis, two.ZAxis), toCentre, 8, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.YAxis, two.XAxis), toCentre, 9, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.YAxis, two.YAxis), toCentre, 10, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.YAxis, two.ZAxis), toCentre, 11, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.ZAxis, two.XAxis), toCentre, 12, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.ZAxis, two.YAxis), toCentre, 13, ref pen, ref best)) { return(false); } if (!TryAxis(one, two, Vector3.Cross(one.ZAxis, two.ZAxis), toCentre, 14, ref pen, ref best)) { return(false); } // Asegurarse de que tenemos un resultado. if (best != uint.MaxValue) { // Tenemos colisión, y tenemos el eje de colisión con menor penetración if (best < 3) { // Hay un vértice la caja dos en una cara de la caja uno. FillPointFaceBoxBox(one, two, toCentre, best, pen, ref data); data.AddContact(); return(true); } else if (best < 6) { // Hay un vértice de la caja uno en una cara de la caja dos. FillPointFaceBoxBox(two, one, toCentre * -1.0f, best - 3, pen, ref data); data.AddContact(); return(true); } else { // Contacto canto a canto. Obtener el eje común. best -= 6; uint oneAxisIndex = best / 3; uint twoAxisIndex = best % 3; Vector3 oneAxis = one.GetAxis((TransformAxis)oneAxisIndex); Vector3 twoAxis = two.GetAxis((TransformAxis)twoAxisIndex); Vector3 axis = Vector3.Cross(oneAxis, twoAxis); axis.Normalize(); // El eje debería apuntar desde la caja uno a la dos. if (Vector3.Dot(axis, toCentre) > 0f) { axis = axis * -1.0f; } // Tenemos los ejes, pero no los cantos. // Cada eje tiene 4 cantos paralelos a él, tenemos que encontrar los 4 de cada caja. // Buscaremos el punto en el centro del canto. Sabemos que su componente en el eje de colisión es 0 y // determinamos cual de los extremos en cada uno de los otros ejes es el más cercano. Vector3 vOne = one.HalfSize; Vector3 vTwo = two.HalfSize; float[] ptOnOneEdge = new float[] { vOne.X, vOne.Y, vOne.Z }; float[] ptOnTwoEdge = new float[] { vTwo.X, vTwo.Y, vTwo.Z }; for (uint i = 0; i < 3; i++) { if (i == oneAxisIndex) { ptOnOneEdge[i] = 0; } else if (Vector3.Dot(one.GetAxis((TransformAxis)i), axis) > 0f) { ptOnOneEdge[i] = -ptOnOneEdge[i]; } if (i == twoAxisIndex) { ptOnTwoEdge[i] = 0; } else if (Vector3.Dot(two.GetAxis((TransformAxis)i), axis) < 0f) { ptOnTwoEdge[i] = -ptOnTwoEdge[i]; } } vOne.X = ptOnOneEdge[0]; vOne.Y = ptOnOneEdge[1]; vOne.Z = ptOnOneEdge[2]; vTwo.X = ptOnTwoEdge[0]; vTwo.Y = ptOnTwoEdge[1]; vTwo.Z = ptOnTwoEdge[2]; // Pasar a coordenadas del mundo vOne = Vector3.Transform(vOne, one.Transform); vTwo = Vector3.Transform(vTwo, two.Transform); // Tenemos un punto y una dirección para los cantos que colisionan. // Necesitamos encontrar el punto de mayor cercanía de los dos segmentos. float[] vOneAxis = new float[] { one.HalfSize.X, one.HalfSize.Y, one.HalfSize.Z }; float[] vTwoAxis = new float[] { two.HalfSize.X, two.HalfSize.Y, two.HalfSize.Z }; Vector3 vertex = ContactPoint( vOne, oneAxis, vOneAxis[oneAxisIndex], vTwo, twoAxis, vTwoAxis[twoAxisIndex], bestSingleAxis > 2); // Llenar el contacto. Contact contact = data.CurrentContact; contact.Penetration = pen; contact.ContactNormal = axis; contact.ContactPoint = vertex; contact.SetBodyData(ref one.Body, ref two.Body, data.Friction, data.Restitution); data.AddContact(); return(true); } } return(false); }
public static bool BoxAndTri(CollisionBox box, Triangle tri) { // Pasar el triángulo a coordenadas de la caja Triangle trnTri = new Triangle( Vector3.Subtract(tri.Point1, box.Position), Vector3.Subtract(tri.Point2, box.Position), Vector3.Subtract(tri.Point3, box.Position)); // Obtener los ejes del triángulo Vector3 edge0 = Vector3.Subtract(trnTri.Point2, trnTri.Point1); Vector3 edge1 = Vector3.Subtract(trnTri.Point3, trnTri.Point2); Vector3 edge2 = Vector3.Subtract(trnTri.Point1, trnTri.Point3); // Comprobar si la caja está sobre el triángulo float fex = Math.Abs(edge0.X); float fey = Math.Abs(edge0.Y); float fez = Math.Abs(edge0.Z); if (!AXISTEST_X01(trnTri.Point1, trnTri.Point3, box.HalfSize, edge0.Z, edge0.Y, fez, fey)) { return(false); } if (!AXISTEST_Y02(trnTri.Point1, trnTri.Point3, box.HalfSize, edge0.Z, edge0.X, fez, fex)) { return(false); } if (!AXISTEST_Z12(trnTri.Point2, trnTri.Point3, box.HalfSize, edge0.Y, edge0.X, fey, fex)) { return(false); } fex = Math.Abs(edge1.X); fey = Math.Abs(edge1.Y); fez = Math.Abs(edge1.Z); if (!AXISTEST_X01(trnTri.Point1, trnTri.Point3, box.HalfSize, edge1.Z, edge1.Y, fez, fey)) { return(false); } if (!AXISTEST_Y02(trnTri.Point1, trnTri.Point3, box.HalfSize, edge1.Z, edge1.X, fez, fex)) { return(false); } if (!AXISTEST_Z0(trnTri.Point1, trnTri.Point2, box.HalfSize, edge1.Y, edge1.X, fey, fex)) { return(false); } fex = Math.Abs(edge2.X); fey = Math.Abs(edge2.Y); fez = Math.Abs(edge2.Z); if (!AXISTEST_X2(trnTri.Point1, trnTri.Point2, box.HalfSize, edge2.Z, edge2.Y, fez, fey)) { return(false); } if (!AXISTEST_Y1(trnTri.Point1, trnTri.Point2, box.HalfSize, edge2.Z, edge2.X, fez, fex)) { return(false); } if (!AXISTEST_Z12(trnTri.Point2, trnTri.Point3, box.HalfSize, edge2.Y, edge2.X, fey, fex)) { return(false); } // Hay intersección return(true); }
/// <summary> /// Detecta la colisión entre una caja y una lista de triángulos /// </summary> /// <param name="box">Caja</param> /// <param name="triangleList">Lista de triángulos</param> /// <param name="data">Datos de colisión a llenar</param> /// <returns>Devuelve verdadero si existe colisión, falso en el resto de los casos</returns> private static bool BoxAndTriangleList(CollisionBox box, Triangle[] triangleList, ref CollisionData data) { if (data.ContactsLeft <= 0) { // Si no hay más contactos disponibles se sale de la función. return(false); } bool intersection = false; if (data.ContactsLeft > 0) { int firstContact = data.ContactCount; foreach (Triangle triangle in triangleList) { // Comprobar la intersección con el triángulo if (IntersectionTests.BoxAndTri(box, triangle)) { if (data.ContactsLeft > 0) { if (CollisionDetector.BoxAndTriangle(box, triangle, ref data)) { intersection = true; } } else { break; } } } //int contactCount = data.ContactCount - firstContact; //if (intersection && contactCount > 1) //{ // //Agrupar los contactos // Vector3 contactPoint = Vector3.Zero; // Vector3 contactNormal = Vector3.Zero; // float penetration = 0f; // for (int i = firstContact; i < data.ContactCount; i++) // { // contactPoint += data.ContactArray[i].ContactPoint; // contactNormal += data.ContactArray[i].ContactNormal; // penetration += data.ContactArray[i].Penetration; // } // contactPoint /= contactCount; // contactNormal /= contactCount; // penetration /= contactCount; // Contact newContact = new Contact(); // data.ContactArray[firstContact].ContactPoint = contactPoint; // data.ContactArray[firstContact].ContactNormal = Vector3.Normalize(contactNormal); // data.ContactArray[firstContact].Penetration = penetration; // data.SetContactIndex(firstContact + 1); //} } return(intersection); }