Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
LayerPeriod.cs
1using System.Drawing;
3
5
6// A lattice of translations, independent of canonical object identity.
7internal readonly record struct LayerPeriod(PointF Columns, PointF Rows, bool WrapColumns, bool WrapRows)
8{
9 internal static LayerPeriod Create(ISceneLayerCoordinates coordinates, SceneLayer layer)
10 {
11 if (layer.GridColumnCount <= 0 || layer.GridRowCount <= 0 || layer.TileWidth <= 0 || layer.TileHeight <= 0)
12 throw new InvalidOperationException("Wrapping requires positive grid and tile dimensions.");
13
14 PointF Period(int columns, int rows, bool enabled)
15 {
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);
19 if (enabled)
20 {
21 // Includes both stagger parities and pixel-rounding phases.
22 for (int x = -2; x <= 2; x++)
23 for (int y = -2; y <= 2; y++)
24 {
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.");
29 }
30 }
31 return delta;
32 }
33
34 var result = new LayerPeriod(Period(layer.GridColumnCount, 0, layer.WrapHorizontally),
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.");
38 return result;
39 }
40
41 internal PointF Coefficients(PointF delta)
42 {
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));
46 }
47
48 internal PointF Offset(double column, double row) =>
49 new((float)(column * Columns.X + row * Rows.X), (float)(column * Columns.Y + row * Rows.Y));
50
51 internal PointF Nearest(PointF point, PointF reference)
52 {
53 var delta = new PointF(reference.X - point.X, reference.Y - point.Y);
54 var coefficients = Coefficients(delta);
55 // For a single axis the Euclidean projection, not the inverse lattice,
56 // gives the closest equivalent point.
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;
69 // Exact bounded search also handles strongly skewed and unequal periods.
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)))
72 {
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; }
76 }
77 return best;
78 }
79
80 internal IEnumerable<PointF> Offsets(RectangleF content, RectangleF query)
81 {
82 // Minkowski difference bounds all translations whose content could intersect query.
83 var corners = new[] {
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++)
97 {
98 var offset = Offset(x, y);
99 var translated = content;
100 translated.Offset(offset);
101 if (translated.IntersectsWith(query))
102 yield return offset;
103 }
104 }
105}
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.