using System.Windows; using OneClickRun.Helpers; using Xunit; namespace OneClickRun.Tests; public class WheelGeometryTests { private const double InnerR = 70; private const double OuterR = 220; private const double Gap = 10; private static readonly Point Center = new(300, 300); /// /// 需求核心:“不论距离中心多远,扇形之间的间距都相等”。 /// 相邻扇形在任意半径处,右边界点与左边界点间的距离应恒等于 Gap。 /// [Fact] public void AdjacentSectorGapIsConstantAtEveryRadius() { var radii = new[] { InnerR, 80, 100, 145, 200, OuterR }; for (var count = 2; count <= 12; count++) { for (var k = 0; k < count; k++) { var rightEdgeAngle = WheelGeometry.SectorStartAngle(k, count) + WheelGeometry.TwoPi / count; var leftEdgeAngle = WheelGeometry.SectorStartAngle((k + 1) % count, count); // 共享同一条原始径向射线(允许相差 2π 整周期) Assert.Equal(0.0, (rightEdgeAngle - leftEdgeAngle) % WheelGeometry.TwoPi, 12); foreach (var rho in radii) { // 扇形 k 的右边界点:角度 - ε(ρ);扇形 k+1 的左边界点:角度 + ε(ρ) var right = WheelGeometry.PointOnCircle(Center, rho, rightEdgeAngle - WheelGeometry.EdgeOffset(rho, Gap)); var left = WheelGeometry.PointOnCircle(Center, rho, leftEdgeAngle + WheelGeometry.EdgeOffset(rho, Gap)); var distance = (right - left).Length; Assert.True(Math.Abs(distance - Gap) < 1e-9, $"count={count}, sector={k}, rho={rho}: gap={distance}, expected={Gap}"); } } } } /// 两个相邻边界点是同一条偏移直线上的点(平行边 ⇒ 处处等距) [Fact] public void AdjacentEdgePointsLieOnParallelOffsetLines() { const int count = 6; const int sector = 2; var edgeAngle = WheelGeometry.SectorStartAngle(sector, count) + WheelGeometry.TwoPi / count; // 同一偏移线在不同半径上的点应共线:取三点验证叉积为 0 var p1 = WheelGeometry.PointOnCircle(Center, 80, edgeAngle + WheelGeometry.EdgeOffset(80, Gap)); var p2 = WheelGeometry.PointOnCircle(Center, 145, edgeAngle + WheelGeometry.EdgeOffset(145, Gap)); var p3 = WheelGeometry.PointOnCircle(Center, 200, edgeAngle + WheelGeometry.EdgeOffset(200, Gap)); var cross = (p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X); Assert.True(Math.Abs(cross) < 1e-9, $"边界点不共线,叉积={cross}"); } [Fact] public void SectorCornersHaveExactRadii() { for (var count = 1; count <= 12; count++) { for (var k = 0; k < count; k++) { var (p1, p2, p3, p4) = WheelGeometry.SectorCorners(Center, InnerR, OuterR, Gap, k, count); Assert.Equal(InnerR, (p1 - Center).Length, 9); Assert.Equal(OuterR, (p2 - Center).Length, 9); Assert.Equal(OuterR, (p3 - Center).Length, 9); Assert.Equal(InnerR, (p4 - Center).Length, 9); } } } [Theory] [InlineData(1)] [InlineData(4)] [InlineData(6)] [InlineData(12)] public void HitTestMapsSectorMidPointsToExpectedSector(int count) { for (var k = 0; k < count; k++) { var mid = WheelGeometry.SectorMidPoint(Center, InnerR, OuterR, k, count); var hit = WheelGeometry.HitTest(mid, Center, InnerR, OuterR, Gap, count); Assert.Equal(k, hit); } } [Fact] public void HitTestReturnsNullForCenterOutsideAndGap() { // 中心区域 Assert.Null(WheelGeometry.HitTest(Center, Center, InnerR, OuterR, Gap, 4)); // 圆环外 Assert.Null(WheelGeometry.HitTest(new Point(Center.X + OuterR + 5, Center.Y), Center, InnerR, OuterR, Gap, 4)); // 间隙正中间:原始径向边界角度处 var midRadius = (InnerR + OuterR) / 2; var edge = WheelGeometry.SectorStartAngle(1, 4); var inGap = WheelGeometry.PointOnCircle(Center, midRadius, edge); Assert.Null(WheelGeometry.HitTest(inGap, Center, InnerR, OuterR, Gap, 4)); } [Fact] public void SingleSectorIsFullRing() { var angles = new[] { -Math.PI / 2, 0.0, Math.PI / 2, Math.PI, Math.PI * 1.5 }; foreach (var angle in angles) { var point = WheelGeometry.PointOnCircle(Center, (InnerR + OuterR) / 2, angle); Assert.Equal(0, WheelGeometry.HitTest(point, Center, InnerR, OuterR, Gap, 1)); } Assert.NotNull(WheelGeometry.CreateSectorGeometry(Center, InnerR, OuterR, Gap, 0, 1)); } [Fact] public void SectorGeometryIsFrozen() { var geometry = WheelGeometry.CreateSectorGeometry(Center, InnerR, OuterR, Gap, 0, 4); Assert.True(geometry.IsFrozen); Assert.False(geometry.IsEmpty()); } }