The mutation table let the guard's removal survive: with saturate's NaN rule (max(0, NaN) = 0) a zero-lifetime cloud has no opacity without it, and ACloudWithNoLifetimeObscuresNothing still pins that behaviour. Builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
32 lines
1.3 KiB
C#
32 lines
1.3 KiB
C#
using CultMath;
|
|
using static CultMath.math;
|
|
using float2 = CultMath.float2;
|
|
|
|
// A vented vapour cloud: a free-floating disc that thins every sensor sight line through it. Not an Entity,
|
|
// never priced or rolled, never saved. Zone.Vent is the only adder and Zone's cloud step the only remover;
|
|
// Zone.Obscuration is the one reader detection uses.
|
|
public sealed class VapourCloud
|
|
{
|
|
public FloatingBodyId Id;
|
|
public KinematicBody Body;
|
|
public double VentedAt;
|
|
public float Lifetime;
|
|
public float Radius;
|
|
// The opacity at the moment of venting; it fades linearly to nothing over Lifetime.
|
|
public float Opacity;
|
|
public Entity Venter;
|
|
|
|
// A cloud with no lifetime divides by zero; saturate maps the NaN and the infinity to nothing left.
|
|
public float OpacityAt(double now) =>
|
|
saturate(Opacity) * saturate(1f - (float) (now - VentedAt) / Lifetime);
|
|
|
|
// Whether the segment a-b reaches the disc: the closest point of the segment to the centre lies within the
|
|
// radius. A cloud past either end of the segment touches it only if it is near that end.
|
|
public bool Touches(float2 a, float2 b)
|
|
{
|
|
var ab = b - a;
|
|
var length2 = lengthsq(ab);
|
|
var t = length2 > 0f ? saturate(dot(Body.Position - a, ab) / length2) : 0f;
|
|
return lengthsq(Body.Position - (a + ab * t)) <= Radius * Radius;
|
|
}
|
|
}
|