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
HexAxialFlatTopCoordinates.cs
1using System.Drawing;
3
5
10internal sealed class HexAxialFlatTopCoordinates : ISceneLayerCoordinates
11{
18 public Point GetAnchorPixelAtSceneLayerCoordinates(SceneLayer sceneLayer, PointF gp)
19 {
20 int width = sceneLayer.TileWidth;
21 int height = sceneLayer.TileHeight;
22
23 // The layout uses even-q offset coordinates. Interpolate the
24 // staggered Y offset between the surrounding integer columns.
25 int baseColumn = (int)MathF.Floor(gp.X);
26 float columnProgress = gp.X - baseColumn;
27
28 // Preserve the existing integer-division behavior for odd tile heights.
29 float halfHeight = height / 2;
30 float currentColumnOffsetY = (baseColumn & 1) == 0 ? 0f : halfHeight;
31 float nextColumnOffsetY = ((baseColumn + 1) & 1) == 0 ? 0f : halfHeight;
32 float offsetY = currentColumnOffsetY + ((nextColumnOffsetY - currentColumnOffsetY) * columnProgress);
33
34 var origin = sceneLayer.OriginPx;
35 float x = -origin.X + (gp.X * width * 0.75f);
36 float y = -origin.Y + (gp.Y * height) + offsetY;
37
38 return new Point((int)MathF.Floor(x), (int)MathF.Floor(y));
39 }
40
48 public PointF GetSceneLayerCoordinatesAtPixel(SceneLayer sceneLayer, PointF pixelPt)
49 {
50 int W = sceneLayer.TileWidth;
51 int H = sceneLayer.TileHeight;
52
53 float originX = sceneLayer.OriginPx.X;
54 float originY = sceneLayer.OriginPx.Y;
55
56 // --- 1) Coarse column estimate using centers, not box top-left ---
57
58 // Center of column 0 is at originX + W/2
59 float dxFromCol0Center = pixelPt.X - (-originX + W * 0.5f);
60
61 // Columns advance by 0.75 * W horizontally
62 float colF = dxFromCol0Center / (W * 0.75f);
63 int approxCol = (int)Math.Round(colF);
64
65 // --- 2) Coarse row estimate using centers and parity ---
66
67 // Center Y of row 0 in this column:
68 // even col: originY + H/2
69 // odd col: originY + H/2 + H/2 = originY + H
70 float baseCenterY =
71 -originY + H * 0.5f
72 + ((approxCol & 1) == 0 ? 0f : H * 0.5f);
73
74 float dyFromRow0Center = pixelPt.Y - baseCenterY;
75 float rowF = dyFromRow0Center / H;
76 int approxRow = (int)Math.Round(rowF);
77
78 var best = new Point(approxCol, approxRow);
79 float bestDist = float.MaxValue;
80
81 // --- 3) Refine via polygon hit-test + nearest center ---
82
83 foreach (var cand in NeighborsFlatTop(approxCol, approxRow, includeSelf: true))
84 {
85 var poly = HexPolygonFlatTop(sceneLayer, cand.X, cand.Y, includeOverhang: false);
86
87 var pInt = new Point((int)Math.Round(pixelPt.X), (int)Math.Round(pixelPt.Y));
88 if (PointInPolygon(poly, pInt))
89 return new PointF(cand.X, cand.Y);
90
91 // Center of candidate hex
92 float cx = -originX + cand.X * (W * 0.75f) + W / 2f;
93 float cy = -originY + cand.Y * (float)H
94 + ((cand.X & 1) == 0 ? 0 : H / 2) + H / 2f;
95
96 float dx = cx - pixelPt.X;
97 float dy = cy - pixelPt.Y;
98 float d = dx * dx + dy * dy;
99
100 if (d < bestDist)
101 {
102 bestDist = d;
103 best = cand;
104 }
105 }
106
107 return new PointF(best.X, best.Y);
108 }
109
117 public List<SceneLayerTile> GetSceneLayerTilesInPixelRange(SceneLayer sceneLayer, Rectangle worldPixelRange, bool includeOverhang)
118 {
119 var result = new List<SceneLayerTile>();
120 int W = sceneLayer.TileWidth;
121 int H = sceneLayer.TileHeight;
122
123 int originX = sceneLayer.OriginPx.X;
124 int originY = sceneLayer.OriginPx.Y;
125 var candidateRange = TileBounds.GetTransformedTileCandidateRange(sceneLayer, worldPixelRange, includeOverhang);
126
127 int minCol = (int)Math.Floor((candidateRange.Left + originX) / (W * 0.75f)) - 2;
128 int maxCol = (int)Math.Ceiling((candidateRange.Right + originX) / (W * 0.75f)) + 2;
129
130 for (int col = minCol; col <= maxCol; col++)
131 {
132 int yOffset = ((col & 1) == 0 ? 0 : H / 2);
133 int minRow = (int)Math.Floor((candidateRange.Top + originY - yOffset) / (float)H) - 2;
134 int maxRow = (int)Math.Ceiling((candidateRange.Bottom + originY - yOffset) / (float)H) + 2;
135
136 for (int row = minRow; row <= maxRow; row++)
137 {
138 var gp = sceneLayer[col, row];
139 if (gp == null) continue;
140 var r = GetPixelRangeForTile(gp, includeOverhang);
141 if (r.IntersectsWith(worldPixelRange))
142 result.Add(gp);
143 }
144 }
145
146 return result;
147 }
148
155 public Rectangle GetPixelRangeForTile(Tile tile, bool includeOverhang)
156 {
157 var p = GetAnchorPixelAtSceneLayerCoordinates(tile.SceneLayer, tile.SceneLayerCoordinates);
158 int W = tile.SceneLayer.TileWidth; int H = tile.SceneLayer.TileHeight;
159 var rect = new Rectangle(p.X, p.Y, W, H); // hex image fits W x H box
160 return TileBounds.ApplyTileGeometry(rect, tile, includeOverhang);
161 }
162
169 public Rectangle GetPixelRangeForTileList(List<Tile> tileList, bool includeOverhang)
170 {
171 Rectangle ret = Rectangle.Empty;
172 foreach (var t in tileList)
173 {
174 var r = GetPixelRangeForTile(t, includeOverhang);
175 ret = ret.IsEmpty ? r : Rectangle.Union(ret, r);
176 }
177 return ret;
178 }
179
187 public SceneLayerTile GetAdjacentSceneLayerTile(SceneLayerTile gp, CardinalDirections dir)
188 {
189 int x = gp.GridCoordinatesAbs.X; int y = gp.GridCoordinatesAbs.Y;
190 bool even = (x & 1) == 0;
191 var m = gp.SceneLayer;
192 return dir switch
193 {
194 CardinalDirections.E => m.ResolveWrappedTile(x + 1, y),
195 CardinalDirections.W => m.ResolveWrappedTile(x - 1, y),
196 CardinalDirections.NE => m.ResolveWrappedTile(x + 1, y - (even ? 1 : 0)),
197 CardinalDirections.SE => m.ResolveWrappedTile(x + 1, y + (even ? 0 : 1)),
198 CardinalDirections.NW => m.ResolveWrappedTile(x - 1, y - (even ? 1 : 0)),
199 CardinalDirections.SW => m.ResolveWrappedTile(x - 1, y + (even ? 0 : 1)),
200 CardinalDirections.N => null, // not a direct neighbor in hex grid
201 CardinalDirections.S => null,
202 _ => null
203 };
204 }
205
212 public Point[] GetPolygonPts(Tile tile, bool includeOverhang)
213 {
214 return HexPolygonFlatTop(tile.SceneLayer, (int)tile.SceneLayerCoordinates.X, (int)tile.SceneLayerCoordinates.Y, includeOverhang);
215 }
216
224 public PointF FindEquivalentSceneLayerCoordinates(PointF valColRow, int xUpperBound, int yUpperBound)
225 {
226 // wrap like square; hex maps are usually finite with wrap disabled, but keep parity sane
227 float modX = valColRow.X % (xUpperBound + 1);
228 float modY = valColRow.Y % (yUpperBound + 1);
229 if (modX < 0) modX += xUpperBound + 1;
230 if (modY < 0) modY += yUpperBound + 1;
231 return new PointF(modX, modY);
232 }
233
234 #region helpers
235
236 private static IEnumerable<Point> NeighborsFlatTop(int col, int row, bool includeSelf)
237 {
238 if (includeSelf) yield return new Point(col, row);
239 bool even = (col & 1) == 0;
240 yield return new Point(col + 1, row);
241 yield return new Point(col - 1, row);
242 yield return new Point(col + 1, row + (even ? 0 : 1));
243 yield return new Point(col + 1, row - (even ? 1 : 0));
244 yield return new Point(col - 1, row + (even ? 0 : 1));
245 yield return new Point(col - 1, row - (even ? 1 : 0));
246 }
247
248 private static Point[] HexPolygonFlatTop(SceneLayer sceneLayer, int col, int row, bool includeOverhang)
249 {
250 int W = sceneLayer.TileWidth;
251 int H = sceneLayer.TileHeight;
252
253 int originX = sceneLayer.OriginPx.X;
254 int originY = sceneLayer.OriginPx.Y;
255
256 var p = new Point(
257 -originX + (int)Math.Floor(col * (W * 0.75f)),
258 -originY + row * H + ((col & 1) == 0 ? 0 : H / 2));
259
260 var rect = new Rectangle(p.X, p.Y, W, H);
261 var ohRect = TileBounds.ApplyOverhang(
262 rect,
263 includeOverhang ? new Spacing(0, 0, 0, 0) : Spacing.None,
264 includeOverhang); // polygon uses base box; overhang impacts range, not shape
265
266 int x = ohRect.X;
267 int y = ohRect.Y;
268
269 // Flat-top vertices from bounding box
270 return new[]
271 {
272 new Point(x + W/4, y),
273 new Point(x + 3*W/4, y),
274 new Point(x + W, y + H/2),
275 new Point(x + 3*W/4, y + H),
276 new Point(x + W/4, y + H),
277 new Point(x, y + H/2)
278 };
279 }
280
281 private static bool PointInPolygon(Point[] poly, Point p)
282 {
283 bool inside = false; int j = poly.Length - 1;
284 for (int i = 0; i < poly.Length; i++)
285 {
286 bool intersect = ((poly[i].Y > p.Y) != (poly[j].Y > p.Y)) &&
287 (p.X < (poly[j].X - poly[i].X) * (p.Y - poly[i].Y) / (float)(poly[j].Y - poly[i].Y) + poly[i].X);
288 if (intersect) inside = !inside; j = i;
289 }
290 return inside;
291 }
292
293 #endregion helpers
294}
Point GridCoordinatesAbs
Gets the absolute grid coordinates of this tile within its parent scene layer as integer values.
override SceneLayer SceneLayer
Gets the parent scene layer that contains this tile.
Point OriginPx
Gets or sets the world-space pixel origin of this layer's (0,0) tile position.
int TileHeight
Gets or sets the height of each tile in this layer, measured in pixels.
int TileWidth
Gets or sets the width of each tile in this layer, measured in pixels.
CardinalDirections
Represents the eight cardinal and intercardinal directions of the compass.
@ Rectangle
Particles are spawned uniformly within a rectangular area. This is the default distribution.
record struct Spacing([property:JsonProperty("left")] int Left, [property:JsonProperty("top")] int Top, [property:JsonProperty("right")] int Right, [property:JsonProperty("bottom")] int Bottom)
Represents dimensions in pixels when spacing Tilesheets.
Definition Spacing.cs:12