GTG Safe Zone
The alarm ring
Around the zone lies a band of, say, 150 m. An enemy who enters it raises the alarm. The band follows every corner of the outline, notches included, and it must never turn into a puzzle of its own.
The obvious version: a bigger polygon
The first idea is to push every edge outwards by the ring width and treat the result as a second polygon. For a convex shape that is fine. For a concave one it is not: when a notch is narrower than twice the ring width, the pushed-out edges cross each other and leave loops that somebody would have to cut away. The shape you test against is then broken exactly where the zone is most interesting.
The robust version: a distance
A position is in the ring when it is outside the zone and not further from the outline than the ring width. That is almost the same set of positions as the pushed-out polygon, only without ever constructing it – mathematically, the outline grown by a circle. It cannot degenerate, and it is cheaper. The one visible difference is that the outer corners are round instead of sharp.
// Shortest distance from a point to a line segment, in the XZ plane.
static float DistanceToSegmentXZ(vector p, vector a, vector b)
{
float abx = b[0] - a[0];
float abz = b[2] - a[2];
float apx = p[0] - a[0];
float apz = p[2] - a[2];
float lenSq = abx * abx + abz * abz;
// How far along the segment the point projects, clamped to 0..1 - outside the segment
// the nearest point is then simply an end. A degenerate segment (two identical points)
// keeps t at 0 and measures to a: correct, and no division by zero.
float t = 0;
if (lenSq > 0.0001)
t = Math.Clamp((apx * abx + apz * abz) / lenSq, 0, 1);
float dx = apx - t * abx;
float dz = apz - t * abz;
return Math.Sqrt(dx * dx + dz * dz);
}
// Shortest distance to the outline, whether the point is inside or outside.
static float DistanceToPolygonEdgeXZ(vector p, notnull array<vector> poly)
{
int count = poly.Count();
if (count < 2)
return float.MAX;
float best = float.MAX;
int j = count - 1;
for (int i = 0; i < count; i++)
{
float d = DistanceToSegmentXZ(p, poly[j], poly[i]);
if (d < best)
best = d;
j = i;
}
return best;
}
The question the alarm scan asks is then two lines, in the same order as every other check in the zone – cheap tests first:
bool IsInsideOrInRing(vector pos)
{
if (!m_bValid || !m_bEnabled || !IsHeightInRange(pos))
return false;
if (MyAD_Geometry.IsPointInPolygonXZ(pos, m_aOutline))
return true;
return MyAD_Geometry.DistanceToPolygonEdgeXZ(pos, m_aOutline) <= m_fRingWidth; // e.g. 150
}
Neutral parties do not trip it
Only a real enemy of the zone's owner raises the alarm. Somebody who is neither friendly nor hostile may stand in the ring as long as they like. Otherwise civilian traffic on a road at the edge would keep the zone armed permanently.