computeVisibilityPolygon function
List<Vector2>
computeVisibilityPolygon({
- required Vector2 center,
- required double radius,
- required List<
LightingOccluder> occluders, - int rays = 64,
Computes the region visible from center, limited by radius and clipped
by occluders.
The result is a polygon whose points are ordered by angle. Returns an empty
list when radius is not positive or when center is inside an occluder
(the light is completely hidden).
rays is the number of evenly spaced rays added to the rays that target the
corners of every occluder. Corners are always sampled, so even a low number
of rays keeps the silhouette recognizable.
Implementation
List<Vector2> computeVisibilityPolygon({
required Vector2 center,
required double radius,
required List<LightingOccluder> occluders,
int rays = 64,
}) {
if (radius <= 0) {
return const [];
}
final segments = <_Segment>[];
for (final occluder in occluders) {
if (_distanceToRect(center, occluder.bounds) > radius) {
continue;
}
final points = occluder.points;
for (var i = 0; i < points.length; i++) {
segments.add(_Segment(points[i], points[(i + 1) % points.length]));
}
}
// A light inside a wall is fully blocked.
for (final occluder in occluders) {
if (occluder.bounds.contains(Offset(center.x, center.y)) &&
pointInPolygon(center, occluder.points)) {
return const [];
}
}
if (segments.isEmpty) {
return _circlePolygon(center, radius, rays);
}
final angles = <double>[];
final rayCount = math.max(8, rays);
for (var i = 0; i < rayCount; i++) {
angles.add((i / rayCount) * math.pi * 2);
}
// Besides the evenly spaced rays, aim at both ends of every segment. The
// small offsets avoid missing a corner when two rays are nearly parallel.
for (final segment in segments) {
for (final point in [segment.a, segment.b]) {
final angle = math.atan2(point.y - center.y, point.x - center.x);
angles
..add(angle - _cornerEpsilon)
..add(angle)
..add(angle + _cornerEpsilon);
}
}
angles.sort();
final result = <Vector2>[];
for (final angle in angles) {
final direction = Vector2(math.cos(angle), math.sin(angle));
var distance = radius;
for (final segment in segments) {
final hit = _rayHit(center, direction, segment);
if (hit != null && hit > _minDistance && hit < distance) {
distance = hit;
}
}
result.add(center + direction * distance);
}
return result;
}