acdream/tests/AcDream.App.Tests/Rendering/Packs/AuthoredCelestialShadowSourceResolverTests.cs

321 lines
11 KiB
C#

using System.Numerics;
using AcDream.App.Rendering.Packs;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering.Packs;
public sealed class AuthoredCelestialShadowSourceResolverTests
{
[Fact]
public void VerifiedDerethIds_AreStable()
{
Assert.Equal(0x01001348u, AuthoredCelestialShadowSourceResolver.SunGfxObjId);
Assert.Equal(0x01001F6Au, AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId);
Assert.Equal(0x01001F67u, AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId);
}
[Fact]
public void SunOverlap_WinsRegardlessOfObjectOrder()
{
DayGroupData group = Group(
Celestial(
AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId,
Vector3.UnitX),
Celestial(
AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId,
Vector3.UnitX),
Celestial(
AuthoredCelestialShadowSourceResolver.SunGfxObjId,
Vector3.UnitX));
AuthoredCelestialShadowSource result = Resolve(group, 0.5f);
Assert.Equal(AuthoredCelestialShadowSourceKind.Sun, result.Kind);
Assert.Equal(2, result.ObjectIndex);
Assert.Equal(AuthoredCelestialShadowSourceResolver.SunGfxObjId, result.GfxObjId);
}
[Fact]
public void MissingSun_FallsBackToDominantMoonBeforeSecondaryMoon()
{
DayGroupData group = Group(
Celestial(
AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId,
Vector3.UnitX),
Celestial(
AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId,
Vector3.UnitX));
AuthoredCelestialShadowSource result = Resolve(group, 0.5f);
Assert.Equal(AuthoredCelestialShadowSourceKind.DominantMoon, result.Kind);
Assert.Equal(1, result.ObjectIndex);
Assert.Equal(
AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId,
result.GfxObjId);
}
[Fact]
public void FullyTransparentHigherPriorityObjects_FallBackToSecondaryMoon()
{
DayGroupData group = Group(
[
Celestial(
AuthoredCelestialShadowSourceResolver.SunGfxObjId,
Vector3.UnitX),
Celestial(
AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId,
Vector3.UnitX),
Celestial(
AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId,
Vector3.UnitX),
],
Replacements(
0f,
Replace(0, transparent: 1f),
Replace(1, transparent: 1f)));
AuthoredCelestialShadowSource result = Resolve(group, 0.5f);
Assert.Equal(AuthoredCelestialShadowSourceKind.SecondaryMoon, result.Kind);
Assert.Equal(2, result.ObjectIndex);
Assert.Equal(
AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId,
result.GfxObjId);
}
[Fact]
public void EffectiveReplacement_ProvidesItsGfxIdentityAndSortCenter()
{
const uint replacementGfxObjId = 0x0100ABCDu;
DayGroupData group = Group(
[
Celestial(
AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId,
-Vector3.UnitX),
],
Replacements(
0f,
Replace(
0,
gfxObjId: replacementGfxObjId,
transparent: 0.35f,
sortCenter: Vector3.UnitX)));
AuthoredCelestialShadowSource result = Resolve(group, 0.5f);
Assert.Equal(AuthoredCelestialShadowSourceKind.DominantMoon, result.Kind);
Assert.Equal(replacementGfxObjId, result.GfxObjId);
AssertVectorClose(Vector3.UnitZ, result.SurfaceToLightDirection);
}
[Fact]
public void ReplacementRotation_IsAppliedBeforeTheSkyArcRotation()
{
DayGroupData group = Group(
[
Celestial(
AuthoredCelestialShadowSourceResolver.SunGfxObjId,
Vector3.UnitY),
],
Replacements(0f, Replace(0, rotate: 90f)));
AuthoredCelestialShadowSource result = Resolve(group, 0.5f);
Assert.Equal(AuthoredCelestialShadowSourceKind.Sun, result.Kind);
AssertVectorClose(Vector3.UnitZ, result.SurfaceToLightDirection);
AssertClose(1f, result.ElevationSin);
}
[Fact]
public void SkyTransformDirection_MatchesTheAuthoredAnalyticTransform()
{
DayGroupData group = Group(
[
Celestial(
AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId,
new Vector3(2f, 1f, 3f),
beginAngle: 0f,
endAngle: 80f,
beginTime: 0f,
endTime: 1f),
],
Replacements(0f, Replace(0, rotate: 30f)));
AuthoredCelestialShadowSource result = Resolve(group, 0.5f);
// Independent analytic result for:
// anchor (2,1,3)
// heading rotation Z(-30 degrees)
// current arc rotation Y(-40 degrees)
// using System.Numerics' row-vector convention.
Vector3 expected = new(
-0.05839998f,
-0.03580622f,
0.99765092f);
Assert.Equal(AuthoredCelestialShadowSourceKind.SecondaryMoon, result.Kind);
AssertVectorClose(expected, result.SurfaceToLightDirection);
AssertClose(expected.Z, result.ElevationSin);
}
[Fact]
public void NoVisibleOrAboveHorizonCandidate_ReturnsNone()
{
DayGroupData group = Group(
Celestial(
AuthoredCelestialShadowSourceResolver.SunGfxObjId,
Vector3.UnitX,
beginAngle: 90f,
endAngle: 90f,
beginTime: 0.1f,
endTime: 0.2f),
Celestial(
AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId,
Vector3.UnitX,
beginAngle: -10f),
Celestial(
AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId,
Vector3.UnitX,
beginAngle: 0f));
AuthoredCelestialShadowSource result = Resolve(group, 0.5f);
Assert.False(result.IsAvailable);
Assert.Equal(AuthoredCelestialShadowSourceKind.None, result.Kind);
Assert.Equal(-1, result.ObjectIndex);
Assert.Equal(0u, result.GfxObjId);
}
[Fact]
public void MidnightWrap_IsVisibleOnBothSidesAndNotAtMidday()
{
DayGroupData group = Group(
Celestial(
AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId,
Vector3.UnitX,
beginAngle: 80f,
endAngle: 100f,
beginTime: 0.9f,
endTime: 0.1f));
AuthoredCelestialShadowSource beforeMidnight = Resolve(group, 0.95f);
AuthoredCelestialShadowSource afterMidnight = Resolve(group, 0.05f);
AuthoredCelestialShadowSource midday = Resolve(group, 0.5f);
Assert.Equal(
AuthoredCelestialShadowSourceKind.SecondaryMoon,
beforeMidnight.Kind);
Assert.Equal(
AuthoredCelestialShadowSourceKind.SecondaryMoon,
afterMidnight.Kind);
Assert.True(beforeMidnight.ElevationSin > 0.99f);
Assert.True(afterMidnight.ElevationSin > 0.99f);
Assert.Equal(AuthoredCelestialShadowSourceKind.None, midday.Kind);
}
[Fact]
public void AuthoredEnergy_ComesFromDirectionalColorTimesBrightness()
{
DayGroupData group = Group(
Celestial(
AuthoredCelestialShadowSourceResolver.SunGfxObjId,
Vector3.UnitX));
SkyKeyframe sky = Sky(
dirColor: new Vector3(0.4f, 0.8f, 0.2f),
dirBright: 0.5f);
AuthoredCelestialShadowSource selected =
AuthoredCelestialShadowSourceResolver.Resolve(group, 0.5f, in sky);
AuthoredCelestialShadowSource noCandidate =
AuthoredCelestialShadowSourceResolver.Resolve(null, 0.5f, in sky);
AssertClose(0.4f, selected.AuthoredEnergy);
AssertClose(0.4f, noCandidate.AuthoredEnergy);
}
private static AuthoredCelestialShadowSource Resolve(
DayGroupData group,
float dayFraction)
{
SkyKeyframe sky = Sky();
return AuthoredCelestialShadowSourceResolver.Resolve(
group,
dayFraction,
in sky);
}
private static DayGroupData Group(params SkyObjectData[] skyObjects) =>
Group(skyObjects, []);
private static DayGroupData Group(
IReadOnlyList<SkyObjectData> skyObjects,
params DatSkyKeyframeData[] skyTimes) => new()
{
Name = "Synthetic",
ChanceOfOccur = 1f,
SkyObjects = skyObjects,
SkyTimes = skyTimes,
};
private static SkyObjectData Celestial(
uint gfxObjId,
Vector3 sortCenter,
float beginAngle = 90f,
float? endAngle = null,
float beginTime = 0f,
float endTime = 0f) => new()
{
GfxObjId = gfxObjId,
AuthoredSortCenter = sortCenter,
BeginTime = beginTime,
EndTime = endTime,
BeginAngle = beginAngle,
EndAngle = endAngle ?? beginAngle,
};
private static DatSkyKeyframeData Replacements(
float begin,
params SkyObjectReplaceData[] replacements) => new()
{
Keyframe = Sky(begin: begin),
Replaces = replacements,
};
private static SkyObjectReplaceData Replace(
uint objectIndex,
uint gfxObjId = 0u,
float rotate = 0f,
float transparent = 0f,
Vector3? sortCenter = null) => new()
{
ObjectIndex = objectIndex,
GfxObjId = gfxObjId,
Rotate = rotate,
Transparent = transparent,
AuthoredSortCenter = sortCenter ?? Vector3.Zero,
};
private static SkyKeyframe Sky(
float begin = 0f,
Vector3? dirColor = null,
float dirBright = 1f) => new(
Begin: begin,
SunHeadingDeg: 90f,
SunPitchDeg: 45f,
DirColor: dirColor ?? Vector3.One,
DirBright: dirBright,
AmbColor: new Vector3(0.2f),
AmbBright: 0.4f,
FogColor: new Vector3(0.4f),
FogDensity: 0f);
private static void AssertVectorClose(Vector3 expected, Vector3 actual)
{
AssertClose(expected.X, actual.X);
AssertClose(expected.Y, actual.Y);
AssertClose(expected.Z, actual.Z);
}
private static void AssertClose(float expected, float actual) =>
Assert.InRange(MathF.Abs(expected - actual), 0f, 1e-5f);
}