VB.NET/C#中任意图形重叠检测及近距离移动实现方法咨询
Hey there! Let's tackle your two main questions—detecting overlaps between arbitrary shapes (like L-shapes and triangles) and moving a triangle to get as close as possible to an L-shape—with practical, code-ready solutions in C# (I’ll note how to adapt this to VB.NET too).
You already have the basics down for circles and rectangles, so let's jump into the go-to method for polygon-based shapes: the Separating Axis Theorem (SAT). It's efficient, easy to implement, and works for all convex polygons. For concave shapes like an L-shape, we just split them into smaller convex shapes (e.g., two rectangles) and check collisions against each part.
How SAT Works (Simplified)
The core idea is: if two convex shapes don't overlap, there's at least one straight line (axis) where their projections don't overlap. To check for overlap:
- Grab all the edge normals from both shapes (these are our candidate axes)
- For each axis, calculate the minimum and maximum projection values for both shapes
- If any axis has non-overlapping projections, the shapes don't collide. If all axes have overlapping projections, they do collide.
C# Example Code
First, let's define some basic shape structures:
public struct Vector2 { public float X; public float Y; public Vector2(float x, float y) { X = x; Y = y; } public static Vector2 operator -(Vector2 a, Vector2 b) => new Vector2(a.X - b.X, a.Y - b.Y); public static Vector2 operator *(Vector2 v, float scalar) => new Vector2(v.X * scalar, v.Y * scalar); public float Dot(Vector2 other) => X * other.X + Y * other.Y; public Vector2 Normalize() { float mag = (float)Math.Sqrt(X*X + Y*Y); return mag == 0 ? this : new Vector2(X/mag, Y/mag); } } public class Polygon { public List<Vector2> Vertices { get; set; } public Polygon(List<Vector2> vertices) { Vertices = vertices; } // Get all edge normals for the polygon public List<Vector2> GetNormals() { List<Vector2> normals = new List<Vector2>(); for (int i = 0; i < Vertices.Count; i++) { Vector2 v1 = Vertices[i]; Vector2 v2 = Vertices[(i+1)%Vertices.Count]; Vector2 edge = v2 - v1; // Perpendicular (normal) vector Vector2 normal = new Vector2(-edge.Y, edge.X).Normalize(); normals.Add(normal); } return normals; } // Get min and max projection values on an axis public (float Min, float Max) GetProjection(Vector2 axis) { float min = Vertices[0].Dot(axis); float max = min; foreach (var v in Vertices) { float proj = v.Dot(axis); if (proj < min) min = proj; if (proj > max) max = proj; } return (min, max); } }
Now the SAT collision check function:
public static bool DoPolygonsOverlap(Polygon a, Polygon b) { // Check all normals from polygon A foreach (var axis in a.GetNormals()) { var projA = a.GetProjection(axis); var projB = b.GetProjection(axis); // Check if projections don't overlap if (projA.Max < projB.Min || projB.Max < projA.Min) return false; } // Check all normals from polygon B foreach (var axis in b.GetNormals()) { var projA = a.GetProjection(axis); var projB = b.GetProjection(axis); if (projA.Max < projB.Min || projB.Max < projA.Min) return false; } // All axes have overlapping projections—shapes collide return true; }
Handling Concave Shapes (Like L-Shapes)
For an L-shape, split it into two convex rectangles. Then check if the triangle overlaps with either rectangle:
// Example: Split L-shape into two rectangles Polygon lShapePart1 = new Polygon(new List<Vector2> { new Vector2(0,0), new Vector2(5,0), new Vector2(5,3), new Vector2(0,3) }); Polygon lShapePart2 = new Polygon(new List<Vector2> { new Vector2(3,0), new Vector2(8,0), new Vector2(8,5), new Vector2(3,5) }); // Check triangle against both parts bool overlapsWithLShape = DoPolygonsOverlap(triangle, lShapePart1) || DoPolygonsOverlap(triangle, lShapePart2);
To move the triangle as close as possible to the L-shape (without overlapping, or just touching), we'll extend the SAT logic to find the minimum separation vector—this tells us exactly how far and in which direction to move the triangle to make contact.
C# Example: Calculate Minimum Separation
public static (Vector2 SeparationVector, float Distance) GetMinimumSeparation(Polygon a, Polygon b) { float minDistance = float.MaxValue; Vector2 bestAxis = new Vector2(); // Check normals from A foreach (var axis in a.GetNormals()) { var projA = a.GetProjection(axis); var projB = b.GetProjection(axis); float distance = Math.Max(projA.Min - projB.Max, projB.Min - projA.Max); if (distance > 0) // No overlap, calculate separation { if (distance < minDistance) { minDistance = distance; bestAxis = axis; } } else // Already overlapping, we need to push apart—use absolute distance { float overlap = -distance; if (overlap < minDistance) { minDistance = overlap; bestAxis = axis; } } } // Check normals from B foreach (var axis in b.GetNormals()) { var projA = a.GetProjection(axis); var projB = b.GetProjection(axis); float distance = Math.Max(projA.Min - projB.Max, projB.Min - projA.Max); if (distance > 0) { if (distance < minDistance) { minDistance = distance; bestAxis = axis; } } else { float overlap = -distance; if (overlap < minDistance) { minDistance = overlap; bestAxis = axis; } } } // Determine direction: we want to move A towards/away from B correctly Vector2 centerA = new Vector2(a.Vertices.Average(v => v.X), a.Vertices.Average(v => v.Y)); Vector2 centerB = new Vector2(b.Vertices.Average(v => v.X), b.Vertices.Average(v => v.Y)); Vector2 direction = centerB - centerA; if (direction.Dot(bestAxis) < 0) bestAxis = bestAxis * -1; return (bestAxis * minDistance, minDistance); }
Move the Triangle
Now use this to adjust the triangle's position:
// Assume we have our triangle and L-shape parts Polygon triangle = new Polygon(new List<Vector2> { new Vector2(10,10), new Vector2(12,10), new Vector2(11,12) }); // Check against both L-shape parts and get the closest separation var separation1 = GetMinimumSeparation(triangle, lShapePart1); var separation2 = GetMinimumSeparation(triangle, lShapePart2); var finalSeparation = separation1.Distance < separation2.Distance ? separation1 : separation2; // Move the triangle by the separation vector to get it as close as possible for (int i = 0; i < triangle.Vertices.Count; i++) { triangle.Vertices[i] = triangle.Vertices[i] + finalSeparation.SeparationVector; }
Adapting to VB.NET
The logic is identical—you just need to adjust the syntax:
- Replace
structwithStructure - Use
List(Of Vector2)instead ofList<Vector2> - Adjust operator overloading (e.g.,
Public Shared Operator -(a As Vector2, b As Vector2) As Vector2) - Replace lambda expressions with
Functiondelegates where needed
Hope this gives you a solid starting point! The SAT method is flexible enough to handle almost any polygon-based shape, and the separation vector calculation lets you precisely position your triangle relative to the L-shape.
内容的提问来源于stack exchange,提问作者fra

