/// <summary> /// If the points are not centered they will be centered with the difference being applied to the localOffset. /// </summary> /// <param name="points">Points.</param> public PolygonCollider( Vector2[] points ) { // first and last point must not be the same. we want an open polygon var isPolygonClosed = points[0] == points[points.Length - 1]; if( isPolygonClosed ) Array.Resize( ref points, points.Length - 1 ); var center = Polygon.findPolygonCenter( points ); setLocalOffset( center ); Polygon.recenterPolygonVerts( points ); shape = new Polygon( points ); }
public PolygonCollider( Vector2[] points ) { // first and last point must not be the same. we want an open polygon var isPolygonClosed = points[0] == points[points.Length - 1]; // create the array with an extra element if we need to close the poly var tempPoints = new Vector2[ isPolygonClosed ? points.Length - 1 : points.Length]; // copy our points over for( var i = 0; i < tempPoints.Length; i++ ) tempPoints[i] = points[i]; shape = new Polygon( tempPoints ); }
public static bool pointToPoly( Vector2 point, Polygon poly, out CollisionResult result ) { result = new CollisionResult(); if( poly.containsPoint( point ) ) { float distanceSquared; var closestPoint = Polygon.getClosestPointOnPolygonToPoint( poly.points, point - poly.position, out distanceSquared, out result.normal ); result.minimumTranslationVector = result.normal * Mathf.sqrt( distanceSquared ); result.point = closestPoint + poly.position; return true; } return false; }
public static bool lineToPoly( Vector2 start, Vector2 end, Polygon polygon, out RaycastHit hit ) { hit = new RaycastHit(); var normal = Vector2.Zero; var intersectionPoint = Vector2.Zero; var fraction = float.MaxValue; var hasIntersection = false; for( int j = polygon.points.Length - 1, i = 0; i < polygon.points.Length; j = i, i++ ) { var edge1 = polygon.position + polygon.points[j]; var edge2 = polygon.position + polygon.points[i]; Vector2 intersection; if( lineToLine( edge1, edge2, start, end, out intersection ) ) { hasIntersection = true; // TODO: is this the correct and most efficient way to get the fraction? // check x fraction first. if it is NaN use y instead var distanceFraction = ( intersection.X - start.X ) / ( end.X - start.X ); if( float.IsNaN( distanceFraction ) ) distanceFraction = ( intersection.Y - start.Y ) / ( end.Y - start.Y ); if( distanceFraction < fraction ) { var edge = edge2 - edge1; normal = new Vector2( edge.Y, -edge.X ); fraction = distanceFraction; intersectionPoint = intersection; } } } if( hasIntersection ) { normal.Normalize(); float distance; Vector2.Distance( ref start, ref intersectionPoint, out distance ); hit.setValues( fraction, distance, intersectionPoint, normal ); return true; } return false; }
static bool polygonToPolygon(Polygon first, Polygon second, Vector2?deltaMovement, out Vector2 responseNormal, out float timeOfCollision) { timeOfCollision = float.MinValue; responseNormal = Vector2.Zero; // polygon verts are in local space so we need to convert one of the polys to be in the space of the other. We use the distance // between them to do so. var polygonOffset = first.position - second.position; // All the separation axes var iNumAxes = 0; if (deltaMovement.HasValue) { _satAxisArray[iNumAxes] = new Vector2(-deltaMovement.Value.Y, deltaMovement.Value.X); var fVel2 = Vector2.Dot(deltaMovement.Value, deltaMovement.Value); if (fVel2 > Mathf.epsilon) { if (!intervalIntersect(first, second, ref _satAxisArray[iNumAxes], ref polygonOffset, ref deltaMovement, out _satTimerPerAxis[iNumAxes])) { return(false); } iNumAxes++; } } // test separation axes of A for (int j = first.points.Length - 1, i = 0; i < first.points.Length; j = i, i++) { // we only need to check 2 axis for boxes if (second.isBox && i == 2) { break; } var point0 = first.points[j]; var point1 = first.points[i]; var edge = point1 - point0; _satAxisArray[iNumAxes] = new Vector2(-edge.Y, edge.X); if (!intervalIntersect(first, second, ref _satAxisArray[iNumAxes], ref polygonOffset, ref deltaMovement, out _satTimerPerAxis[iNumAxes])) { return(false); } iNumAxes++; } // test separation axes of B for (int j = second.points.Length - 1, i = 0; i < second.points.Length; j = i, i++) { // we only need to check 2 axis for boxes if (second.isBox && i == 2) { break; } var point0 = second.points[j]; var point1 = second.points[i]; var edge = point1 - point0; _satAxisArray[iNumAxes] = new Vector2(-edge.Y, edge.X); if (!intervalIntersect(first, second, ref _satAxisArray[iNumAxes], ref polygonOffset, ref deltaMovement, out _satTimerPerAxis[iNumAxes])) { return(false); } iNumAxes++; } if (!findMinimumTranslationDistance(iNumAxes, out responseNormal, out timeOfCollision)) { return(false); } // make sure the polygons gets pushed away from each other. if (Vector2.Dot(responseNormal, polygonOffset) < 0f) { responseNormal = -responseNormal; } return(true); }
/// <summary> /// checks for a collision between first and second. deltaMovement is applied to first to see if a collision occurs in the future. /// - a negative timeOfCollision means we have an overlap /// - a positive timeOfCollision means we have a future collision /// based on http://elancev.name/oliver/2D%20polygon.htm /// </summary> /// <returns><c>true</c>, if to polygon was polygoned, <c>false</c> otherwise.</returns> /// <param name="first">First.</param> /// <param name="second">Second.</param> /// <param name="deltaMovement">Delta movement.</param> /// <param name="responseNormal">Response normal.</param> /// <param name="timeOfCollision">Time of collision.</param> static bool polygonToPolygon( Polygon first, Polygon second, Vector2? deltaMovement, out Vector2 responseNormal, out float timeOfCollision ) { timeOfCollision = float.MinValue; responseNormal = Vector2.Zero; // polygon verts are in local space so we need to convert one of the polys to be in the space of the other. We use the distance // between them to do so. var polygonOffset = first.position - second.position; // All the separation axes var iNumAxes = 0; if( deltaMovement.HasValue ) { _satAxisArray[iNumAxes] = new Vector2( -deltaMovement.Value.Y, deltaMovement.Value.X ); var fVel2 = Vector2.Dot( deltaMovement.Value, deltaMovement.Value ); if( fVel2 > Mathf.epsilon ) { if( !intervalIntersect( first, second, ref _satAxisArray[iNumAxes], ref polygonOffset, ref deltaMovement, out _satTimerPerAxis[iNumAxes] ) ) return false; iNumAxes++; } } // test separation axes of A for( int j = first.points.Length - 1, i = 0; i < first.points.Length; j = i, i++ ) { // we only need to check 2 axis for boxes if( second.isBox && i == 2 ) break; var point0 = first.points[j]; var point1 = first.points[i]; var edge = point1 - point0; _satAxisArray[iNumAxes] = new Vector2( -edge.Y, edge.X ); if( !intervalIntersect( first, second, ref _satAxisArray[iNumAxes], ref polygonOffset, ref deltaMovement, out _satTimerPerAxis[iNumAxes] ) ) return false; iNumAxes++; } // test separation axes of B for( int j = second.points.Length - 1, i = 0; i < second.points.Length; j = i, i++ ) { // we only need to check 2 axis for boxes if( second.isBox && i == 2 ) break; var point0 = second.points[j]; var point1 = second.points[i]; var edge = point1 - point0; _satAxisArray[iNumAxes] = new Vector2( -edge.Y, edge.X ); if( !intervalIntersect( first, second, ref _satAxisArray[iNumAxes], ref polygonOffset, ref deltaMovement, out _satTimerPerAxis[iNumAxes] ) ) return false; iNumAxes++; } if( !findMinimumTranslationDistance( iNumAxes, out responseNormal, out timeOfCollision ) ) return false; // make sure the polygons gets pushed away from each other. if( Vector2.Dot( responseNormal, polygonOffset ) < 0f ) responseNormal = -responseNormal; return true; }
static bool intervalIntersect( Polygon first, Polygon second, ref Vector2 axis, ref Vector2 shapeOffset, ref Vector2? deltaMovement, out float taxis ) { taxis = float.MinValue; float min0, max0; float min1, max1; getInterval( first, first.points.Length, axis, out min0, out max0 ); getInterval( second, second.points.Length, axis, out min1, out max1 ); var h = Vector2.Dot( shapeOffset, axis ); min0 += h; max0 += h; var d0 = min0 - max1; // if overlapped, d0 < 0 var d1 = min1 - max0; // if overlapped, d1 < 0 // separated, test dynamic intervals if( d0 > 0.0f || d1 > 0.0f ) { // if we have no velocity we are done if( !deltaMovement.HasValue ) return false; var v = Vector2.Dot( deltaMovement.Value, axis ); // small velocity, so only the overlap test will be relevant. if( Math.Abs( v ) < 0.0000001f ) return false; var t0 = -d0 / v; // time of impact to d0 reaches 0 var t1 = d1 / v; // time of impact to d0 reaches 1 if( t0 > t1 ) { var temp = t0; t0 = t1; t1 = temp; } taxis = t0 > 0.0f ? t0 : t1; if( taxis < 0.0f || taxis > 1.0f ) return false; return true; } else { // overlap. get the interval, as a the smallest of |d0| and |d1| // return negative number to mark it as an overlap taxis = d0 > d1 ? d0 : d1; return true; } }
/// <summary> /// calculates the projection range of a polygon along an axis /// </summary> /// <param name="A">A.</param> /// <param name="iNumVertices">I number vertices.</param> /// <param name="xAxis">X axis.</param> /// <param name="min">Minimum.</param> /// <param name="max">Max.</param> static void getInterval( Polygon polygon, int numVertices, Vector2 axis, out float min, out float max ) { min = max = Vector2.Dot( polygon.points[0], axis ); for( var i = 1; i < numVertices; i++ ) { var d = Vector2.Dot( polygon.points[i], axis ); if( d < min ) min = d; else if( d > max ) max = d; } }
// something isnt right here with this one static bool circleToPolygon2( Circle circle, Polygon polygon, out CollisionResult result ) { result = new CollisionResult(); var closestPointIndex = -1; var poly2Circle = circle.position - polygon.position; var poly2CircleNormalized = Vector2.Normalize( poly2Circle ); var max = float.MinValue; for( var i = 0; i < polygon.points.Length; i++ ) { var projection = Vector2.Dot( polygon.points[i], poly2CircleNormalized ); if( max < projection ) { closestPointIndex = i; max = projection; } } var poly2CircleLength = poly2Circle.Length(); if( poly2CircleLength - max - circle.radius > 0 && poly2CircleLength > 0 ) return false; // we have a collision // find the closest point on the polygon. we know the closest index so we only have 2 edges to test var prePointIndex = closestPointIndex - 1; var postPointIndex = closestPointIndex + 1; // handle wrapping the points if( prePointIndex < 0 ) prePointIndex = polygon.points.Length - 1; if( postPointIndex == polygon.points.Length ) postPointIndex = 0; var circleCenter = circle.position - polygon.position; var closest1 = closestPointOnLine( polygon.points[prePointIndex], polygon.points[closestPointIndex], circleCenter ); var closest2 = closestPointOnLine( polygon.points[closestPointIndex], polygon.points[postPointIndex], circleCenter ); float distance1, distance2; Vector2.DistanceSquared( ref circleCenter, ref closest1, out distance1 ); Vector2.DistanceSquared( ref circleCenter, ref closest2, out distance2 ); var radiusSquared = circle.radius * circle.radius; float seperationDistance; if( distance1 < distance2 ) { // make sure the squared distance is less than our radius squared else we are not colliding if( distance1 > radiusSquared ) return false; seperationDistance = circle.radius - Mathf.sqrt( distance1 ); var edge = polygon.points[closestPointIndex] - polygon.points[prePointIndex]; result.normal = new Vector2( edge.Y, -edge.X ); result.point = polygon.position + closest1; } else { // make sure the squared distance is less than our radius squared else we are not colliding if( distance2 > radiusSquared ) return false; seperationDistance = circle.radius - Mathf.sqrt( distance2 ); var edge = polygon.points[postPointIndex] - polygon.points[closestPointIndex]; result.normal = new Vector2( edge.Y, -edge.X ); result.point = polygon.position + closest2; } result.normal.Normalize(); result.minimumTranslationVector = result.normal * -seperationDistance; return true; }
// something isnt right here with this one static bool CircleToPolygon2(Circle circle, Polygon polygon, out CollisionResult result) { result = new CollisionResult(); var closestPointIndex = -1; var poly2Circle = circle.position - polygon.position; var poly2CircleNormalized = Vector2.Normalize(poly2Circle); var max = float.MinValue; for (var i = 0; i < polygon.Points.Length; i++) { var projection = Vector2.Dot(polygon.Points[i], poly2CircleNormalized); if (max < projection) { closestPointIndex = i; max = projection; } } var poly2CircleLength = poly2Circle.Length(); if (poly2CircleLength - max - circle.Radius > 0 && poly2CircleLength > 0) { return(false); } // we have a collision // find the closest point on the polygon. we know the closest index so we only have 2 edges to test var prePointIndex = closestPointIndex - 1; var postPointIndex = closestPointIndex + 1; // handle wrapping the points if (prePointIndex < 0) { prePointIndex = polygon.Points.Length - 1; } if (postPointIndex == polygon.Points.Length) { postPointIndex = 0; } var circleCenter = circle.position - polygon.position; var closest1 = ClosestPointOnLine(polygon.Points[prePointIndex], polygon.Points[closestPointIndex], circleCenter); var closest2 = ClosestPointOnLine(polygon.Points[closestPointIndex], polygon.Points[postPointIndex], circleCenter); float distance1, distance2; Vector2.DistanceSquared(ref circleCenter, ref closest1, out distance1); Vector2.DistanceSquared(ref circleCenter, ref closest2, out distance2); var radiusSquared = circle.Radius * circle.Radius; float seperationDistance; if (distance1 < distance2) { // make sure the squared distance is less than our radius squared else we are not colliding if (distance1 > radiusSquared) { return(false); } seperationDistance = circle.Radius - Mathf.Sqrt(distance1); var edge = polygon.Points[closestPointIndex] - polygon.Points[prePointIndex]; result.Normal = new Vector2(edge.Y, -edge.X); result.Point = polygon.position + closest1; } else { // make sure the squared distance is less than our radius squared else we are not colliding if (distance2 > radiusSquared) { return(false); } seperationDistance = circle.Radius - Mathf.Sqrt(distance2); var edge = polygon.Points[postPointIndex] - polygon.Points[closestPointIndex]; result.Normal = new Vector2(edge.Y, -edge.X); result.Point = polygon.position + closest2; } result.Normal.Normalize(); result.MinimumTranslationVector = result.Normal * -seperationDistance; return(true); }
/// <summary> /// does an overlap check of first vs second. ShapeCollisionResult retuns the data for moving first so it isn't colliding with second. /// </summary> /// <returns><c>true</c>, if to polygon was polygoned, <c>false</c> otherwise.</returns> /// <param name="first">First.</param> /// <param name="second">Second.</param> /// <param name="result">Result.</param> public static bool polygonToPolygon( Polygon first, Polygon second, out CollisionResult result ) { result = new CollisionResult(); float timeOfCollision; if( polygonToPolygon( first, second, null, out result.normal, out timeOfCollision ) ) { result.minimumTranslationVector = result.normal * ( timeOfCollision ); return true; } return false; }
public static bool circleToPolygon( Circle circle, Polygon polygon, out CollisionResult result ) { result = new CollisionResult(); // circle position in the polygons coordinates var poly2Circle = circle.position - polygon.position; // first, we need to find the closest distance from the circle to the polygon float distanceSquared; var closestPoint = polygon.getClosestPointOnPolygonToPoint( poly2Circle, out distanceSquared, out result.normal ); // make sure the squared distance is less than our radius squared else we are not colliding if( distanceSquared > circle.radius * circle.radius ) return false; // figure out the mtd var distance = Mathf.sqrt( distanceSquared ); var mtv = ( poly2Circle - closestPoint ) * ( ( circle.radius - distance ) / distance ); result.minimumTranslationVector = -mtv; result.normal.Normalize(); return true; }
/// <summary> /// checks for a collision between two Polygons /// </summary> /// <returns>The collision.</returns> /// <param name="first">Polygon a.</param> /// <param name="second">Polygon b.</param> public static bool polygonToPolygon( Polygon first, Polygon second, out CollisionResult result ) { result = new CollisionResult(); var isIntersecting = true; var firstEdges = first.edgeNormals; var secondEdges = second.edgeNormals; var minIntervalDistance = float.PositiveInfinity; var translationAxis = new Vector2(); var polygonOffset = first.position - second.position; Vector2 axis; // Loop through all the edges of both polygons for( var edgeIndex = 0; edgeIndex < firstEdges.Length + secondEdges.Length; edgeIndex++ ) { // 1. Find if the polygons are currently intersecting // Polygons have the normalized axis perpendicular to the current edge cached for us if( edgeIndex < firstEdges.Length ) axis = firstEdges[edgeIndex]; else axis = secondEdges[edgeIndex - firstEdges.Length]; // Find the projection of the polygon on the current axis float minA = 0; float minB = 0; float maxA = 0; float maxB = 0; var intervalDist = 0f; getInterval( axis, first, ref minA, ref maxA ); getInterval( axis, second, ref minB, ref maxB ); // get our interval to be space of the second Polygon. Offset by the difference in position projected on the axis. float relativeIntervalOffset; Vector2.Dot( ref polygonOffset, ref axis, out relativeIntervalOffset ); minA += relativeIntervalOffset; maxA += relativeIntervalOffset; // check if the polygon projections are currentlty intersecting intervalDist = intervalDistance( minA, maxA, minB, maxB ); if( intervalDist > 0 ) isIntersecting = false; // for Poly-to-Poly casts add a Vector2? parameter called deltaMovement. In the interest of speed we do not use it here // 2. Now find if the polygons *will* intersect. only bother checking if we have some velocity //if( deltaMovement.HasValue ) //{ // // Project the velocity on the current axis // var velocityProjection = Vector2.Dot( axis, deltaMovement.Value ); // // Get the projection of polygon A during the movement // if( velocityProjection < 0 ) // minA += velocityProjection; // else // maxA += velocityProjection; // // Do the same test as above for the new projection // intervalDist = intervalDistance( minA, maxA, minB, maxB ); // if( intervalDist > 0 ) // willIntersect = false; //} // If the polygons are not intersecting and won't intersect, exit the loop if( !isIntersecting ) return false; // Check if the current interval distance is the minimum one. If so store the interval distance and the current distance. // This will be used to calculate the minimum translation vector intervalDist = Math.Abs( intervalDist ); if( intervalDist < minIntervalDistance ) { minIntervalDistance = intervalDist; translationAxis = axis; if( Vector2.Dot( translationAxis, polygonOffset ) < 0 ) translationAxis = -translationAxis; } } // The minimum translation vector can be used to push the polygons appart. result.normal = translationAxis; result.minimumTranslationVector = -translationAxis * minIntervalDistance; return true; }
static void getInterval( Vector2 axis, Polygon polygon, ref float min, ref float max ) { // To project a point on an axis use the dot product float dot; Vector2.Dot( ref polygon.points[0], ref axis, out dot ); min = max = dot; for( var i = 1; i < polygon.points.Length; i++ ) { Vector2.Dot( ref polygon.points[i], ref axis, out dot ); if( dot < min ) min = dot; else if( dot > max ) max = dot; } }
public static bool circleToPolygon( Circle circle, Polygon polygon, out CollisionResult result ) { result = new CollisionResult(); // circle position in the polygons coordinates var poly2Circle = circle.position - polygon.position; // first, we need to find the closest distance from the circle to the polygon float distanceSquared; var closestPoint = Polygon.getClosestPointOnPolygonToPoint( polygon.points, poly2Circle, out distanceSquared, out result.normal ); // make sure the squared distance is less than our radius squared else we are not colliding. Note that if the Circle is fully // contained in the Polygon the distance could be larger than the radius. Because of that we also make sure the circle position // is not inside the poly. var circleCenterInsidePoly = polygon.containsPoint( circle.position ); if( distanceSquared > circle.radius * circle.radius && !circleCenterInsidePoly ) return false; // figure out the mtv. We have to be careful to deal with circles fully contained in the polygon or with their center contained. Vector2 mtv; if( circleCenterInsidePoly ) { mtv = result.normal * ( Mathf.sqrt( distanceSquared ) - circle.radius ); } else { // if we have no distance that means the circle center is on the polygon edge. Move it only by its radius if( distanceSquared == 0 ) { mtv = result.normal * circle.radius; } else { var distance = Mathf.sqrt( distanceSquared ); mtv = -( poly2Circle - closestPoint ) * ( ( circle.radius - distance ) / distance ); } } result.minimumTranslationVector = mtv; result.point = closestPoint + polygon.position; return true; }
/// <summary> /// checks for a collision between two Polygons /// </summary> /// <returns>The collision.</returns> /// <param name="first">Polygon a.</param> /// <param name="second">Polygon b.</param> public static bool polygonToPolygon(Polygon first, Polygon second, out CollisionResult result) { result = new CollisionResult(); var isIntersecting = true; var firstEdges = first.edgeNormals; var secondEdges = second.edgeNormals; var minIntervalDistance = float.PositiveInfinity; var translationAxis = new Vector2(); var polygonOffset = first.position - second.position; Vector2 axis; // Loop through all the edges of both polygons for (var edgeIndex = 0; edgeIndex < firstEdges.Length + secondEdges.Length; edgeIndex++) { // 1. Find if the polygons are currently intersecting // Polygons have the normalized axis perpendicular to the current edge cached for us if (edgeIndex < firstEdges.Length) { axis = firstEdges[edgeIndex]; } else { axis = secondEdges[edgeIndex - firstEdges.Length]; } // Find the projection of the polygon on the current axis float minA = 0; float minB = 0; float maxA = 0; float maxB = 0; var intervalDist = 0f; getInterval(axis, first, ref minA, ref maxA); getInterval(axis, second, ref minB, ref maxB); // get our interval to be space of the second Polygon. Offset by the difference in position projected on the axis. float relativeIntervalOffset; Vector2.Dot(ref polygonOffset, ref axis, out relativeIntervalOffset); minA += relativeIntervalOffset; maxA += relativeIntervalOffset; // check if the polygon projections are currentlty intersecting intervalDist = intervalDistance(minA, maxA, minB, maxB); if (intervalDist > 0) { isIntersecting = false; } // for Poly-to-Poly casts add a Vector2? parameter called deltaMovement. In the interest of speed we do not use it here // 2. Now find if the polygons *will* intersect. only bother checking if we have some velocity //if( deltaMovement.HasValue ) //{ // // Project the velocity on the current axis // var velocityProjection = Vector2.Dot( axis, deltaMovement.Value ); // // Get the projection of polygon A during the movement // if( velocityProjection < 0 ) // minA += velocityProjection; // else // maxA += velocityProjection; // // Do the same test as above for the new projection // intervalDist = intervalDistance( minA, maxA, minB, maxB ); // if( intervalDist > 0 ) // willIntersect = false; //} // If the polygons are not intersecting and won't intersect, exit the loop if (!isIntersecting) { return(false); } // Check if the current interval distance is the minimum one. If so store the interval distance and the current distance. // This will be used to calculate the minimum translation vector intervalDist = Math.Abs(intervalDist); if (intervalDist < minIntervalDistance) { minIntervalDistance = intervalDist; translationAxis = axis; if (Vector2.Dot(translationAxis, polygonOffset) < 0) { translationAxis = -translationAxis; } } } // The minimum translation vector can be used to push the polygons appart. result.normal = translationAxis; result.minimumTranslationVector = -translationAxis * minIntervalDistance; return(true); }
public PolygonCollider( int vertCount, float radius ) { shape = new Polygon( vertCount, radius ); }
/// <summary> /// casts first at second /// </summary> /// <returns><c>true</c>, if to polygon was polygoned, <c>false</c> otherwise.</returns> /// <param name="first">First.</param> /// <param name="second">Second.</param> /// <param name="deltaMovement">Delta movement.</param> public static bool polygonToPolygonCast( Polygon first, Polygon second, Vector2 deltaMovement, out RaycastHit hit ) { hit = new RaycastHit(); float timeOfCollision; if( polygonToPolygon( first, second, deltaMovement, out hit.normal, out timeOfCollision ) ) { hit.fraction = timeOfCollision; // if timeOfCollision is less than 0 this is an overlap if( timeOfCollision < 0f ) { hit.centroid = first.position - hit.normal * timeOfCollision; } else { hit.centroid = first.position + deltaMovement * timeOfCollision; } return true; } return false; }