7internal readonly record
struct LayerPeriod(PointF Columns, PointF Rows, bool WrapColumns, bool WrapRows)
9 internal static LayerPeriod Create(ISceneLayerCoordinates coordinates, SceneLayer layer)
12 throw new InvalidOperationException(
"Wrapping requires positive grid and tile dimensions.");
14 PointF
Period(
int columns,
int rows,
bool enabled)
16 var origin = coordinates.GetAnchorPixelAtSceneLayerCoordinates(layer, PointF.Empty);
17 var end = coordinates.GetAnchorPixelAtSceneLayerCoordinates(layer,
new(columns, rows));
18 var delta =
new PointF(end.X - origin.X, end.Y - origin.Y);
22 for (
int x = -2; x <= 2; x++)
23 for (
int y = -2; y <= 2; y++)
25 var a = coordinates.GetAnchorPixelAtSceneLayerCoordinates(layer,
new(x, y));
26 var b = coordinates.GetAnchorPixelAtSceneLayerCoordinates(layer,
new(x + columns, y + rows));
27 if (b.X - a.X != delta.X || b.Y - a.Y != delta.Y)
28 throw new InvalidOperationException($
"{layer.CoordinateSystemType} cannot repeat cleanly with these dimensions. Flat-top hex column wrapping requires even columns; pointed-top hex row wrapping requires even rows. Pixel-rounded projections also require an integral world-space period.");
36 if (Math.Abs(result.Columns.X * result.Rows.Y - result.Columns.Y * result.Rows.X) < 0.001)
37 throw new InvalidOperationException(
"Wrapping requires independent, nonzero world-space period vectors.");
41 internal PointF Coefficients(PointF delta)
43 double determinant = (double)Columns.X * Rows.Y - (
double)Columns.Y * Rows.X;
44 return new((float)((delta.X * (double)Rows.Y - delta.Y * (double)Rows.X) / determinant),
45 (float)((Columns.X * (double)delta.Y - Columns.Y * (double)delta.X) / determinant));
48 internal PointF Offset(
double column,
double row) =>
49 new((float)(column * Columns.X + row * Rows.X), (float)(column * Columns.Y + row * Rows.Y));
51 internal PointF Nearest(PointF point, PointF reference)
53 var delta =
new PointF(reference.X - point.X, reference.Y - point.Y);
54 var coefficients = Coefficients(delta);
57 double c = WrapColumns ? Math.Round(coefficients.X) : 0;
58 double r = WrapRows ? Math.Round(coefficients.Y) : 0;
59 if (WrapColumns && !WrapRows)
60 c = Math.Round(((
double)delta.X * Columns.X + (
double)delta.Y * Columns.Y) / ((
double)Columns.X * Columns.X + (
double)Columns.Y * Columns.Y));
61 if (WrapRows && !WrapColumns)
62 r = Math.Round(((
double)delta.X * Rows.X + (
double)delta.Y * Rows.Y) / ((
double)Rows.X * Rows.X + (
double)Rows.Y * Rows.Y));
63 var initial = Offset(c, r);
64 var best =
new PointF(point.X + initial.X, point.Y + initial.Y);
65 if (!WrapColumns || !WrapRows)
return best;
66 double Distance(PointF p) => Math.Pow(p.X - reference.X, 2) + Math.Pow(p.Y - reference.Y, 2);
67 double distance = Distance(best);
68 float radius = (float)Math.Sqrt(distance) + 1;
70 foreach (var offset
in Offsets(
new(point.X, point.Y, 0.001f, 0.001f),
71 new(reference.X - radius, reference.Y - radius, radius * 2, radius * 2)))
73 var candidate =
new PointF(point.X + offset.X, point.Y + offset.Y);
74 double next = Distance(candidate);
75 if (next < distance) { best = candidate; distance = next; }
80 internal IEnumerable<PointF> Offsets(RectangleF content, RectangleF query)
84 Coefficients(
new(query.Left - content.Right, query.Top - content.Bottom)),
85 Coefficients(
new(query.Right - content.Left, query.Top - content.Bottom)),
86 Coefficients(
new(query.Left - content.Right, query.Bottom - content.Top)),
87 Coefficients(
new(query.Right - content.Left, query.Bottom - content.Top)) };
88 double minX = WrapColumns ? Math.Ceiling(corners.Min(p => p.X)) : 0;
89 double maxX = WrapColumns ? Math.Floor(corners.Max(p => p.X)) : 0;
90 double minY = WrapRows ? Math.Ceiling(corners.Min(p => p.Y)) : 0;
91 double maxY = WrapRows ? Math.Floor(corners.Max(p => p.Y)) : 0;
92 double count = Math.Max(0, maxX - minX + 1) * Math.Max(0, maxY - minY + 1);
93 if (!
double.IsFinite(count) || count > 1_000_000 || Math.Abs(minX) >
int.MaxValue || Math.Abs(maxX) >
int.MaxValue || Math.Abs(minY) >
int.MaxValue || Math.Abs(maxY) >
int.MaxValue)
94 throw new InvalidOperationException(
"The requested wrapped region exceeds the supported instance range.");
95 for (
double x = minX; x <= maxX; x++)
96 for (
double y = minY; y <= maxY; y++)
98 var offset = Offset(x, y);
99 var translated = content;
100 translated.Offset(offset);
101 if (translated.IntersectsWith(query))
bool WrapVertically
Gets or sets a value indicating whether this layer repeats along its row axis at the grid boundaries.
bool WrapHorizontally
Gets or sets a value indicating whether this layer repeats along its column axis at the grid boundari...
int GridRowCount
Gets the number of rows (tiles high) in this layer's grid.
int TileHeight
Gets or sets the height of each tile in this layer, measured in pixels.
int GridColumnCount
Gets the number of columns (tiles wide) in this layer's grid.
int TileWidth
Gets or sets the width of each tile in this layer, measured in pixels.