/// <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); contact.SetBodyData(ref one.Body, ref two.Body, data.Friction, data.Restitution); }
/// <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); }