private Rectangle _GetNearbyTiles(float x, float y, float width, float height, float pad) { // use offset from tile map center. x -= _position.X; y -= _position.Y; if (Rotation != 0.0f) { // need to transform world-space box to tile map aligned box which includes // original box. Rotation2D rotation = new Rotation2D(MathHelper.ToRadians(Rotation)); // figure tile space position Vector2 newPos = new Vector2(x, y); newPos = rotation.Unrotate(newPos); x = newPos.X; y = newPos.Y; // compute tile space width/height, expanded to include all // of original box in tile aligned box Vector2 xvec = rotation.X; Vector2 yvec = rotation.Y; float newWidth = width * Math.Abs(xvec.X) + height * Math.Abs(xvec.Y); float newHeight = width * Math.Abs(yvec.X) + height * Math.Abs(yvec.Y); width = newWidth; height = newHeight; } float minX, minY, maxX, maxY; minX = ((x - (width * 0.5f)) / _tileSize.X) + (_mapSize.X * 0.5f); maxX = ((x + (width * 0.5f)) / _tileSize.X) + (_mapSize.X * 0.5f); minY = ((y - (height * 0.5f)) / _tileSize.Y) + (_mapSize.Y * 0.5f); maxY = ((y + (height * 0.5f)) / _tileSize.Y) + (_mapSize.Y * 0.5f); minX -= pad; minY -= pad; maxX += pad; maxY += pad; // make sure that returned rectangle only contains valid tile boundaries minX = MathHelper.Clamp(minX, 0.0f, _mapSize.X - 1); maxX = MathHelper.Clamp(maxX + 1, 0.0f, _mapSize.X - 1); minY = MathHelper.Clamp(minY, 0.0f, _mapSize.Y - 1); maxY = MathHelper.Clamp(maxY + 1, 0.0f, _mapSize.Y - 1); return new Rectangle((int)minX, (int)minY, (int)(maxX), (int)(maxY)); }
public static bool IntersectMovingPolyPoly( float elapsedTime, Vector2[] srcVertexList, Vector2[] dstVertexList, Vector2 srcPosition, Vector2 dstPosition, float srcRotation, float dstRotation, Vector2 srcVelocity, Vector2 dstVelocity, ref Vector2 collisionPosition, ref Vector2 collisionNormal, ref Vector2 collisionPenetration, ref float time) { if (srcVertexList == null || dstVertexList == null) return false; int srcVertexCount = srcVertexList.Length; int dstVertexCount = dstVertexList.Length; //src Rotation2D srcRot = new Rotation2D(MathHelper.ToRadians(srcRotation)); //dst Rotation2D dstInverseRot = new Rotation2D(MathHelper.ToRadians(-dstRotation)); Rotation2D refLocalRot = srcRot * dstInverseRot; Vector2 offset = srcPosition - dstPosition; Vector2 refLocalOffset = dstInverseRot.Rotate(offset); Vector2 velocity = srcVelocity - dstVelocity; Vector2 refLocalVelocity = dstInverseRot.Rotate(velocity); float refVelSqr = Vector2.Dot(refLocalVelocity, refLocalVelocity); int axesCount = 0; float fullTimeStep = elapsedTime; float Epsilon = 0.0001f; // Ignore small velocities! if (refVelSqr > Epsilon) { // Set Axis. _vertexAxis[axesCount] = new Vector2(-refLocalVelocity.Y, refLocalVelocity.X); // Check Interval Intersection. if (!_CheckIntervalIntersection( srcVertexList, dstVertexList, ref _vertexAxis[axesCount], ref refLocalOffset, ref refLocalVelocity, ref refLocalRot, ref _timeAxis[axesCount], fullTimeStep)) // No Collision! return false; // Next Axes. axesCount++; } // Test Seperation Axes for Source Object. // NOTE:- We ignore if it's a point! if (srcVertexCount > 1) { for (int j = srcVertexCount - 1, i = 0; i < srcVertexCount; j = i, i++) { // fetch the edge Vector2 dP = srcVertexList[i] - srcVertexList[j]; Vector2 dP2 = new Vector2(-dP.Y, dP.X); _vertexAxis[axesCount] = refLocalRot.Rotate(dP2); // check interval intersection if (!_CheckIntervalIntersection(srcVertexList, dstVertexList, ref _vertexAxis[axesCount], ref refLocalOffset, ref refLocalVelocity, ref refLocalRot, ref _timeAxis[axesCount], fullTimeStep)) return false; //next axes axesCount++; } } // Test Seperation Axes for Destination Object. // NOTE:- We ignore if it's a point! if (dstVertexCount > 1) { for (int j = dstVertexCount - 1, i = 0; i < dstVertexCount; j = i, i++) { // fetch the edge Vector2 dP = dstVertexList[i] - dstVertexList[j]; // set the axis _vertexAxis[axesCount] = new Vector2(-dP.Y, dP.X); if (!_CheckIntervalIntersection(srcVertexList, dstVertexList, ref _vertexAxis[axesCount], ref refLocalOffset, ref refLocalVelocity, ref refLocalRot, ref _timeAxis[axesCount], fullTimeStep)) return false; //next axes axesCount++; } } // Test Special-Case for Segments for Destination Object. if (dstVertexCount == 2) { // Set Axis. _vertexAxis[axesCount] = (dstVertexList[1] - dstVertexList[0]); // Check Interval Intersection. if (!_CheckIntervalIntersection(srcVertexList, dstVertexList, ref _vertexAxis[axesCount], ref refLocalOffset, ref refLocalVelocity, ref refLocalRot, ref _timeAxis[axesCount], fullTimeStep)) // No Collision! return false; // Next Axes. axesCount++; } // Test Special-Case for Segments for Source Object. if (srcVertexCount == 2) { // Set Axis. Vector2 segment = (srcVertexList[1] - srcVertexList[0]); _vertexAxis[axesCount] = refLocalRot.Rotate(segment); // Check Interval Intersection. if (!_CheckIntervalIntersection(srcVertexList, dstVertexList, ref _vertexAxis[axesCount], ref refLocalOffset, ref refLocalVelocity, ref refLocalRot, ref _timeAxis[axesCount], fullTimeStep)) // No Collision! return false; // Next Axes. axesCount++; } // find minimum seperation distance if (!_FindCollisionTime(_vertexAxis, _timeAxis, axesCount, ref collisionNormal, ref time)) return false; // Respace Normal. collisionNormal = dstInverseRot.Unrotate(collisionNormal); // need this below (simple transpose) Rotation2D dstRot = dstInverseRot.Invert(); // compute offset between poly centers for following calculation Vector2 polyCenter = new Vector2(); foreach (Vector2 v in srcVertexList) polyCenter += v; polyCenter *= 1.0f / (float)srcVertexList.Length; Vector2 polyOffset = srcPosition + srcRot.Rotate(polyCenter); polyCenter = new Vector2(); foreach (Vector2 v in dstVertexList) polyCenter += v; polyCenter *= 1.0f / (float)dstVertexList.Length; polyOffset -= dstPosition + dstRot.Rotate(polyCenter); // Make sure the collision polygons are pushed away. // NOTE: This is the overlap case. if (Vector2.Dot(collisionNormal, polyOffset) < 0.0f) collisionNormal = -collisionNormal; int contactCount = 0; if (!_FindContactPoints( srcVertexList, srcPosition, srcVelocity, ref srcRot, dstVertexList, dstPosition, dstVelocity, ref dstRot, collisionNormal, time, _srcContacts, _dstContacts, out contactCount)) // No Support Points! return false; collisionPosition = 0.5f * (_srcContacts[0] + _dstContacts[0]); collisionPenetration = _dstContacts[0] - _srcContacts[0]; if (contactCount == 2) { collisionPosition = 0.5f * collisionPosition + 0.25f * (_srcContacts[1] + _dstContacts[1]); Vector2 collisionPenetration2 = _dstContacts[1] - _srcContacts[1]; if (collisionPenetration2.LengthSquared() > collisionPenetration.LengthSquared()) collisionPenetration = collisionPenetration2; } // return true to indicate collision return true; }
//----------------------------------------------------------------------------------------------- // Find Support Points. // NOTE:- This is a convex shape along a specified direction. //----------------------------------------------------------------------------------------------- static int _FindSupportPoints( Vector2[] poly, Vector2 position, Vector2 velocity, ref Rotation2D rotation, Vector2 collisionNormal, float collisionTime, Vector2[] supportPoints) { // Calculate Normal. Vector2 normal = rotation.Unrotate(collisionNormal); // Reset Direction. float supportMin = _support[0] = Vector2.Dot(poly[0], normal); // Interate Polygon. for (int i = 1; i < poly.Length; i++) { // Calculate. _support[i] = Vector2.Dot(poly[i], normal); // Check For Minimum. if (_support[i] < supportMin) supportMin = _support[i]; } // The limit here is two support-points only. // If we find more then we use the extremums. int supportCount = 0; // Set Threshold. float threshold = 1.0e-3f; // Reset Sign Flag. bool sign = false; // Calculate Perpendicular Normal. Vector2 perpNormal = new Vector2(-collisionNormal.Y, collisionNormal.X); // Interate Polygon. for (int i = 0; i < poly.Length; i++) { // Check Contact. if (_support[i] < supportMin + threshold) { // Transform Contact to World-Space. Vector2 contact; _TransformContact(poly[i], position, velocity, ref rotation, collisionTime, out contact); // Contact Dot. float contactDot = Vector2.Dot(contact, perpNormal); // Less than two supports? if (supportCount < 2) { // Yes, so note contact. _supportMinMax[supportCount] = contactDot; supportPoints[supportCount] = contact; // Increase Support Count. supportCount++; // Note Sign for two contacts. if (supportCount > 1) sign = (_supportMinMax[1] > _supportMinMax[0]); } else { int idx0 = 0; int idx1 = 1; if (!sign) TorqueUtil.Swap(ref idx0, ref idx1); if (contactDot < _supportMinMax[idx0]) { _supportMinMax[idx0] = contactDot; supportPoints[idx0] = contact; } else if (contactDot > _supportMinMax[idx1]) { _supportMinMax[idx1] = contactDot; supportPoints[idx1] = contact; } } } } // Return Support Count. return supportCount; }
static bool _CheckIntervalIntersection(Vector2[] srcVertexList, Vector2[] dstVertexList, ref Vector2 vertexAxis, ref Vector2 refLocalOffset, ref Vector2 refLocalVelocity, ref Rotation2D refLocalRot, ref float timeAxis, float collisionTime) { // calculate intervals for source/destination Vector2 vertexAxisRotated = refLocalRot.Unrotate(vertexAxis); // Get the interval of the polygons projected onto the axis. Min returned in x, max in y. Vector2 srcProj = _CalculateInterval(srcVertexList, ref vertexAxisRotated); Vector2 dstProj = _CalculateInterval(dstVertexList, ref vertexAxis); // add reference offset float srcOffset = Vector2.Dot(refLocalOffset, vertexAxis); srcProj += new Vector2(srcOffset, srcOffset); // calculate intervals // Calculate delta of min of one interval with max of the other. // Note that if the intervals overlap then both deltas must be negative. // I.e., delta>0 --> intervals don't overlap because one min greater than // the other max. float delta0 = srcProj.X - dstProj.Y; float delta1 = dstProj.X - srcProj.Y; // Are we seperated? if (delta0 > 0.0f || delta1 > 0.0f) { // Yes, so test the dynamic intervals // calculate speed along a particular axis float speed = Vector2.Dot(refLocalVelocity, vertexAxis); float epsilon = 0.0001f; // ignore small speeds if (Math.Abs(speed) < epsilon) return false; // The smallest absolute value must be the separation. The reason is that one // delta measures separation between intervals, the other measures the span of // the two intervals (we want separation, which is always smaller). Convert separation // into time by dividing by the speed (adjust sign depending on whether we are going // from source to dest or not). timeAxis = delta0 * delta0 < delta1 * delta1 ? -delta0 / speed : delta1 / speed; // successful collision? return timeAxis >= 0.0f && timeAxis <= collisionTime; } else { // No, so we're overlapped. // // Let's get the interval of the smallest |delta0| and |delta1| and // encode it as a negative value to signify an overlap rather than // a disjoint collision in forward-time. timeAxis = (delta0 > delta1) ? delta0 : delta1; // Successful Collision! return true; } }