rayIntersection method
Returns whether the RaycastResult if the ray intersects the polygon.
If out is defined that is used to populate with the result and then
returned, to minimize the creation of new objects.
Implementation
RaycastResult<ShapeHitbox>? rayIntersection(
Ray2 ray, {
RaycastResult<ShapeHitbox>? out,
}) {
final vertices = globalVertices();
var closestDistance = double.infinity;
LineSegment? closestSegment;
var crossings = 0;
var isOverlappingPoint = false;
// Float32List (used by Vector2) carries ~7 significant digits. After
// reflecting, the stored origin can drift by up to |coord| * 2^-23.
// Scale epsilon to the origin's magnitude so we skip self-intersections
// without missing real hits.
final epsilon =
max(
1.0,
max(ray.origin.x.abs(), ray.origin.y.abs()),
) *
1e-4;
for (var i = 0; i < vertices.length; i++) {
final lineSegment = getEdge(i, vertices: vertices);
final distance = ray.lineSegmentIntersection(lineSegment);
if (distance != null && distance > epsilon) {
crossings++;
if (distance < closestDistance) {
isOverlappingPoint = false;
closestDistance = distance;
closestSegment = lineSegment;
} else if (distance == closestDistance) {
isOverlappingPoint = true;
}
}
}
if (crossings > 0) {
final intersectionPoint = ray.point(
closestDistance,
out: out?.intersectionPoint,
);
// This is "from" to "to" since it is defined ccw in the canvas
// coordinate system
_temporaryNormal
..setFrom(closestSegment!.from)
..sub(closestSegment.to);
_temporaryNormal
..setValues(_temporaryNormal.y, -_temporaryNormal.x)
..normalize();
var isInsideHitbox = false;
if (crossings == 1 || isOverlappingPoint) {
_temporaryNormal.invert();
isInsideHitbox = true;
}
final reflectionDirection =
(out?.reflectionRay?.direction ?? Vector2.zero())
..setFrom(ray.direction)
..reflect(_temporaryNormal);
// Reflect() can introduce sub-epsilon drift. Normalize to keep Ray2's
// unit-length assertion satisfied.
reflectionDirection.normalize();
final reflectionRay =
(out?.reflectionRay?..setWith(
origin: intersectionPoint,
direction: reflectionDirection,
)) ??
Ray2(origin: intersectionPoint, direction: reflectionDirection);
return (out ?? RaycastResult<ShapeHitbox>())..setWith(
hitbox: this as T,
reflectionRay: reflectionRay,
normal: _temporaryNormal,
distance: closestDistance,
isInsideHitbox: isInsideHitbox,
);
}
out?.reset();
return null;
}