Fix ambient flyer cadence and rotating shadows
This commit is contained in:
parent
9cd429dd73
commit
8b7b601b32
5 changed files with 239 additions and 3 deletions
|
|
@ -438,6 +438,41 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The fitted frustum slice bounds receivers, not the casters that project
|
||||
/// onto them. At low celestial elevations a modest tree can be many metres
|
||||
/// farther along the light ray than its visible ground shadow. Padding by
|
||||
/// less than the effective receiver reach therefore clips a caster as the
|
||||
/// camera-relative slice turns, producing partial or disappearing shadows.
|
||||
/// This expands depth only; cascade XY density, draw count, and caster
|
||||
/// membership are unchanged.
|
||||
/// </summary>
|
||||
internal static float ResolveCasterDepthPaddingMeters(
|
||||
float configuredPaddingMeters,
|
||||
float qualityReachMeters,
|
||||
float residentMaximumReachMeters)
|
||||
{
|
||||
if (!float.IsFinite(configuredPaddingMeters)
|
||||
|| configuredPaddingMeters <= 0f)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(configuredPaddingMeters));
|
||||
}
|
||||
if (!float.IsFinite(qualityReachMeters) || qualityReachMeters <= 0f)
|
||||
throw new ArgumentOutOfRangeException(nameof(qualityReachMeters));
|
||||
if (float.IsNaN(residentMaximumReachMeters)
|
||||
|| residentMaximumReachMeters <= 0f)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(residentMaximumReachMeters));
|
||||
}
|
||||
|
||||
float effectiveReceiverReach = MathF.Min(
|
||||
qualityReachMeters,
|
||||
residentMaximumReachMeters);
|
||||
return MathF.Max(configuredPaddingMeters, effectiveReceiverReach);
|
||||
}
|
||||
|
||||
internal DirectionalSunShadowDiagnostics RenderPrepared(
|
||||
IGpuFrame frame,
|
||||
in DirectionalShadowEnvironmentState environment,
|
||||
|
|
@ -473,6 +508,11 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
nameof(worldTransforms));
|
||||
|
||||
long started = Stopwatch.GetTimestamp();
|
||||
float effectiveCasterDepthPaddingMeters =
|
||||
ResolveCasterDepthPaddingMeters(
|
||||
casterDepthPaddingMeters,
|
||||
_quality.MaximumReachMeters,
|
||||
residentMaximumReachMeters);
|
||||
var fit = new DirectionalShadowCascadeFitInput(
|
||||
cameraView,
|
||||
cameraProjection,
|
||||
|
|
@ -480,7 +520,7 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
_quality,
|
||||
cameraNearMeters,
|
||||
PracticalSplitLambda: 0.65f,
|
||||
casterDepthPaddingMeters,
|
||||
effectiveCasterDepthPaddingMeters,
|
||||
residentMaximumReachMeters);
|
||||
int cascadeCount = DirectionalShadowCascadeFitter.Fit(
|
||||
fit,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
private const double FrameEpsilon = 0.000199999995;
|
||||
private const float MaximumElapsed = 2f;
|
||||
|
||||
// The eight retail SetOmega-authored ambient-flyer animations use their
|
||||
// vector as a per-animation-quantum turn. Their animation data is authored
|
||||
// at 30 fps; replaying that raw turn once per modern host/render frame
|
||||
// makes orbit speed scale with monitor refresh (6x at 180 Hz). Keep the
|
||||
// exact authored result at 30 Hz while making the DAT-scenery projection
|
||||
// independent of host cadence. Live PhysicsBody owners retain the literal
|
||||
// CPhysicsObj branch below and are deliberately not changed here.
|
||||
private const double DatStaticOmegaReferenceHz = 30d;
|
||||
|
||||
private sealed class Owner
|
||||
{
|
||||
public required WorldEntity Entity;
|
||||
|
|
@ -468,10 +477,17 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
// static has no CPhysicsObj of its own, so the WorldEntity IS
|
||||
// the frame retail would be rotating — and the render
|
||||
// projection re-reads entity.Rotation every frame, so the
|
||||
// parts composed below orbit it.
|
||||
// parts composed below orbit it. SetOmega's authored vector is
|
||||
// a 30 Hz per-animation-quantum turn; normalize it at this
|
||||
// modern host boundary so monitor refresh cannot change the
|
||||
// flight period.
|
||||
owner.RootFrameScratch.Origin = owner.Entity.Position;
|
||||
owner.RootFrameScratch.Orientation = owner.Entity.Rotation;
|
||||
FrameOps.GRotate(owner.RootFrameScratch, owner.Omega);
|
||||
float omegaScale = (float)(
|
||||
ownerElapsed * DatStaticOmegaReferenceHz);
|
||||
FrameOps.GRotate(
|
||||
owner.RootFrameScratch,
|
||||
owner.Omega * omegaScale);
|
||||
owner.Entity.Rotation = owner.RootFrameScratch.Orientation;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue