OMROpticMath.cs
using Sandbox;
using System;
/// <summary>
/// Shared optic projection math. Keeping PiP rendering and look-sensitivity
/// matching on the same FOV model prevents the scope from visually showing one
/// magnification while input scaling assumes another.
/// </summary>
public static class OMROpticMath
{
public static float ResolveFullScopeFieldOfView(
float sourceFieldOfView,
OMROpticProfile profile
)
{
float source = MathX.Clamp( sourceFieldOfView, 1f, 179f );
if ( profile is null || !profile.IsValid )
return source;
if ( profile.UseNominalMagnificationForPiP )
{
float magnification = MathF.Max( profile.NominalMagnification, 1f );
float sourceRadians = source * (MathF.PI / 180f);
float scopedRadians = 2f * MathF.Atan(
MathF.Tan( sourceRadians * 0.5f ) / magnification
);
return MathX.Clamp(
scopedRadians * (180f / MathF.PI),
1f,
179f
);
}
float scale = MathX.Clamp( profile.AimFovScale, 0.05f, 1f );
return MathX.Clamp( source * scale, 1f, 179f );
}
/// <summary>
/// FOV physically rendered by the optic. A real fixed-power optic remains
/// magnified even when the shooter's eye is not yet centered behind it; what
/// changes during ADS is primarily eye-box/clarity, not the glass suddenly
/// gaining magnification.
/// </summary>
public static float ResolveRenderedScopeFieldOfView(
float sourceFieldOfView,
OMROpticProfile profile,
float aimFraction
)
{
float source = MathX.Clamp( sourceFieldOfView, 1f, 179f );
float full = ResolveFullScopeFieldOfView( source, profile );
if ( profile is null || !profile.IsValid )
return source;
if ( profile.PiPMagnificationMode == OMRPiPMagnificationMode.ConstantOptical )
return full;
return MathX.Lerp(
source,
full,
MathX.Clamp( aimFraction, 0f, 1f )
);
}
/// <summary>
/// Input sensitivity should still blend into the scoped value as the player
/// aims, even when a tiny hip-fire lens preview is already optically 4x.
/// </summary>
public static float ResolveSensitivityFieldOfView(
float sourceFieldOfView,
OMROpticProfile profile,
float aimFraction
)
{
float source = MathX.Clamp( sourceFieldOfView, 1f, 179f );
float full = ResolveFullScopeFieldOfView( source, profile );
return MathX.Lerp(
source,
full,
MathX.Clamp( aimFraction, 0f, 1f )
);
}
public static float CalculateAngularMagnification(
float sourceFieldOfView,
float scopedFieldOfView
)
{
float source = MathX.Clamp( sourceFieldOfView, 1f, 179f ) * (MathF.PI / 180f);
float scoped = MathX.Clamp( scopedFieldOfView, 1f, 179f ) * (MathF.PI / 180f);
float scopedTan = MathF.Tan( scoped * 0.5f );
if ( MathF.Abs( scopedTan ) <= 0.00001f )
return 1f;
return MathF.Max(
MathF.Tan( source * 0.5f ) / scopedTan,
1f
);
}
}