For a game I'm working on I'm trying to write some code that places GameObjects around the perimeter of a PolygonCollider2D. In my game, an object can touch a platform and then starts spreading a subtance around that platform. I want the substance to procedurally spread around the platform, placing GameObjects every x
units. For an example of what I mean, please take a look at this .gif where I did the same thing with RayCasts.
Trying to do this with raycasts introduced a lot of edge cases. To eliminate these, I want to apply a more consistent method.
In Unity, a collider holds an array of Collider.points
which holds the coordinates of the points that make up the collider. In theory, if you start placing gameobjects at point[0]
, look at the direction of point[1]
, and start placing objects in that direction untill you reach point[1]
, look at the direction of point[2]
and repeat, you should be able to neatly place objects around the perimeter of said collider.
My problem is that I wouldn't know whether my initial spreader object would have to start this object placing algorithm between point[0]
and point[1]
or point[n]
and point[n 1]
.
Please take a look at this example:
If my collision happens on the red marker, I would need to somehow figure out that the collision took place on line segment E between point[4]
and point[5]
, so I could then know the 'starting position' along the perimeter and start writing code that places the objects in both directions simultaneously along the perimeter.
My first thought was finding the world position of the collision, and finding the world positions of the closest two points in points[]
to that collision point . But in the above example, that wouldn't work - it'd find positions 2
and 4
(which is not even a segment), even though the collision is touching the line segment between 4
and 5
(segment E).
Does anyone have any suggestions on how to do this?
CodePudding user response:
If you already have your contact point you could go through all the vertices (point
) and check to which line the contact point is closest.
Following two methods are taken from
As a pseudo algorithm:
define point = collisionPoint;
define pair;
define minDistance;
for pair := (p1, p2) in collider.points:
if (dist := shortestDistance(point, pair) < minDistance):
minDistance = dist
pair = (p1, p2)
At the end of this loop, we would have the two points we're looking for. Here's a rough implementation I came up with:
private (Vector2, Vector2)? FindClosestLine(Vector2 contactPoint)
{
var localScale = transform.localScale;
var points = polygonCollider.points;
(Vector2, Vector2) closestLine = (default, default);
var shortestDistance = float.MaxValue;
for (var i = 1; i < points.Length; i )
{
// We multiply the points by localScale, because the collider
// scales them to 1 internally, regardless of our size.
var line = (points[i - 1] * localScale, points[i] * localScale);
var distance = MinDistPointToLine(contactPoint, line);
if (distance < shortestDistance)
{
shortestDistance = distance;
closestLine = line;
}
}
if (shortestDistance < float.MaxValue)
return closestLine;
else return null;
}
To figure out the shortest distance, we can use some basic trigonometry (again, please excuse the drawing.)
The code would look something like this:
private static float MinDistPointToLine(Vector2 point, (Vector2, Vector2) line)
{
// Calculate the shortest distance between the line (p[n 1] - p[n]) and the given point.
var (end, start) = line;
var lineLength = (start - end).magnitude;
var lineLengthSqr = lineLength * lineLength;
var distToStartSqr = (point - end).sqrMagnitude;
var distToEndSqr = (point - start).sqrMagnitude;
// Equation found by algebra.
return distToStartSqr - (distToStartSqr - distToEndSqr - lineLengthSqr) / 2 * lineLength;
}
In this case, we're just printing the two points, but you would obviously use them to implement the algorithm you described.