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
RenderTileQuery.cs
1using System.Drawing;
3
5
13internal readonly record struct RenderTileCandidates(
14 List<SceneLayerTile> Tiles,
15 bool IsRenderOrdered);
16
21internal static class RenderTileQuery
22{
23 internal static RenderTileCandidates GetCandidates(
24 ISceneLayerCoordinates coordinates,
25 SceneLayer layer,
26 Rectangle worldPixelRange,
27 bool includeOverhang)
28 {
29 var tiles = new List<SceneLayerTile>();
30
31 VisitCandidates(
32 coordinates,
33 layer,
34 worldPixelRange,
35 includeOverhang,
36 tile =>
37 {
38 tiles.Add(tile);
39 return true;
40 });
41
42 return new(tiles, IsRenderOrdered(layer));
43 }
44
55 internal static bool VisitCandidates(
56 ISceneLayerCoordinates coordinates,
57 SceneLayer layer,
58 Rectangle worldPixelRange,
59 bool includeOverhang,
60 Func<SceneLayerTile, bool> visitor)
61 {
62 ArgumentNullException.ThrowIfNull(visitor);
63
64 return coordinates switch
65 {
66 OrthogonalCoordinates =>
67 VisitOrthogonal(coordinates, layer, worldPixelRange, includeOverhang, visitor),
68 IsometricRhombicCoordinates =>
69 VisitIsometricRhombic(coordinates, layer, worldPixelRange, includeOverhang, visitor),
70 IsometricAxialCoordinates =>
71 VisitIsometricAxial(coordinates, layer, worldPixelRange, includeOverhang, visitor),
72 HexAxialFlatTopCoordinates =>
73 VisitHexFlat(coordinates, layer, worldPixelRange, includeOverhang, visitor),
75 VisitHexPointed(coordinates, layer, worldPixelRange, includeOverhang, visitor),
76 ObliqueRightCoordinates or ObliqueLeftCoordinates =>
77 VisitOblique(coordinates, layer, worldPixelRange, includeOverhang, visitor),
78 _ => VisitExactFallback(coordinates, layer, worldPixelRange, includeOverhang, visitor)
79 };
80 }
81
82 internal static bool IsRenderOrdered(SceneLayer layer) =>
83 layer.TransformedTiles.Count == 0 ||
84 layer.TileWidth == layer.TileHeight;
85
86 internal static Rectangle GetFixedTileCellBounds(
87 SceneLayer layer,
88 SceneLayerTile tile)
89 {
90 Point grid = tile.GridCoordinatesAbs;
91 int width = layer.TileWidth;
92 int height = layer.TileHeight;
93
94 if (layer.CoordinateSystem is OrthogonalCoordinates)
95 {
96 return new Rectangle(
97 grid.X * width - layer.OriginPx.X,
98 grid.Y * height - layer.OriginPx.Y,
99 width,
100 height);
101 }
102
103 Point anchor = layer.CoordinateSystem.GetAnchorPixelAtSceneLayerCoordinates(
104 layer,
105 grid);
106
107 if (layer.CoordinateSystem is IsometricRhombicCoordinates or IsometricAxialCoordinates)
108 return new Rectangle(anchor.X - width / 2, anchor.Y, width, height);
109
110 return new Rectangle(anchor.X, anchor.Y, width, height);
111 }
112
113 private static bool VisitOrthogonal(
114 ISceneLayerCoordinates coordinates,
115 SceneLayer layer,
116 Rectangle query,
117 bool includeOverhang,
118 Func<SceneLayerTile, bool> visitor)
119 {
120 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
121 var ul = coordinates.GetSceneLayerCoordinatesAtPixel(
122 layer,
123 new PointF(candidate.Left, candidate.Top));
124 var br = coordinates.GetSceneLayerCoordinatesAtPixel(
125 layer,
126 new PointF(candidate.Right - 1, candidate.Bottom - 1));
127
128 int minX = Math.Max(0, (int)Math.Floor(ul.X) - 1);
129 int maxX = Math.Min(layer.GridColumnCount - 1, (int)Math.Ceiling(br.X) + 1);
130 int minY = Math.Max(0, (int)Math.Floor(ul.Y) - 1);
131 int maxY = Math.Min(layer.GridRowCount - 1, (int)Math.Ceiling(br.Y) + 1);
132
133 return VisitRowMajor(layer, minX, maxX, minY, maxY, visitor);
134 }
135
136 private static bool VisitIsometricRhombic(
137 ISceneLayerCoordinates coordinates,
138 SceneLayer layer,
139 Rectangle query,
140 bool includeOverhang,
141 Func<SceneLayerTile, bool> visitor)
142 {
143 GetCornerBounds(
144 coordinates,
145 layer,
146 query,
147 includeOverhang,
148 1,
149 out int minX,
150 out int maxX,
151 out int minY,
152 out int maxY);
153
154 if (!Clamp(layer, ref minX, ref maxX, ref minY, ref maxY))
155 return true;
156
157 for (int depth = minX + minY; depth <= maxX + maxY; depth++)
158 {
159 int xStart = Math.Max(minX, depth - maxY);
160 int xEnd = Math.Min(maxX, depth - minY);
161
162 for (int x = xStart; x <= xEnd; x++)
163 {
164 int y = depth - x;
165 if (layer[x, y] is { } tile && !visitor(tile))
166 return false;
167 }
168 }
169
170 return true;
171 }
172
173 private static bool VisitIsometricAxial(
174 ISceneLayerCoordinates coordinates,
175 SceneLayer layer,
176 Rectangle query,
177 bool includeOverhang,
178 Func<SceneLayerTile, bool> visitor)
179 {
180 GetCornerBounds(
181 coordinates,
182 layer,
183 query,
184 includeOverhang,
185 2,
186 out int minX,
187 out int maxX,
188 out int minY,
189 out int maxY);
190
191 return VisitRowMajorClamped(layer, minX, maxX, minY, maxY, visitor);
192 }
193
194 private static bool VisitHexPointed(
195 ISceneLayerCoordinates coordinates,
196 SceneLayer layer,
197 Rectangle query,
198 bool includeOverhang,
199 Func<SceneLayerTile, bool> visitor)
200 {
201 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
202 int width = layer.TileWidth;
203 int height = layer.TileHeight;
204
205 int minRow = Math.Max(
206 0,
207 (int)Math.Floor((candidate.Top + layer.OriginPx.Y) / (height * 0.75f)) - 2);
208 int maxRow = Math.Min(
209 layer.GridRowCount - 1,
210 (int)Math.Ceiling((candidate.Bottom + layer.OriginPx.Y) / (height * 0.75f)) + 2);
211
212 if (minRow > maxRow)
213 return true;
214
215 for (int row = minRow; row <= maxRow; row++)
216 {
217 int xOffset = (row & 1) == 0 ? 0 : width / 2;
218 int minCol = Math.Max(
219 0,
220 (int)Math.Floor(
221 (candidate.Left + layer.OriginPx.X - xOffset) / (float)width) - 2);
222 int maxCol = Math.Min(
223 layer.GridColumnCount - 1,
224 (int)Math.Ceiling(
225 (candidate.Right + layer.OriginPx.X - xOffset) / (float)width) + 2);
226
227 for (int col = minCol; col <= maxCol; col++)
228 {
229 if (layer[col, row] is { } tile && !visitor(tile))
230 return false;
231 }
232 }
233
234 return true;
235 }
236
237 private static bool VisitHexFlat(
238 ISceneLayerCoordinates coordinates,
239 SceneLayer layer,
240 Rectangle query,
241 bool includeOverhang,
242 Func<SceneLayerTile, bool> visitor)
243 {
244 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
245 int width = layer.TileWidth;
246 int height = layer.TileHeight;
247 int halfHeight = height / 2;
248
249 int minCol = Math.Max(
250 0,
251 (int)Math.Floor((candidate.Left + layer.OriginPx.X) / (width * 0.75f)) - 2);
252 int maxCol = Math.Min(
253 layer.GridColumnCount - 1,
254 (int)Math.Ceiling((candidate.Right + layer.OriginPx.X) / (width * 0.75f)) + 2);
255
256 if (minCol > maxCol)
257 return true;
258
259 int minEven =
260 (int)Math.Floor((candidate.Top + layer.OriginPx.Y) / (float)height) - 2;
261 int maxEven =
262 (int)Math.Ceiling((candidate.Bottom + layer.OriginPx.Y) / (float)height) + 2;
263 int minOdd =
264 (int)Math.Floor(
265 (candidate.Top + layer.OriginPx.Y - halfHeight) / (float)height) - 2;
266 int maxOdd =
267 (int)Math.Ceiling(
268 (candidate.Bottom + layer.OriginPx.Y - halfHeight) / (float)height) + 2;
269
270 int minRow = Math.Max(0, Math.Min(minEven, minOdd));
271 int maxRow = Math.Min(layer.GridRowCount - 1, Math.Max(maxEven, maxOdd));
272 if (minRow > maxRow)
273 return true;
274
275 for (int depth = minRow * 2; depth <= maxRow * 2 + 1; depth++)
276 {
277 for (int col = minCol; col <= maxCol; col++)
278 {
279 int numerator = depth - (col & 1);
280 if ((numerator & 1) != 0)
281 continue;
282
283 int row = numerator / 2;
284 if (row < minRow || row > maxRow)
285 continue;
286
287 if (layer[col, row] is { } tile && !visitor(tile))
288 return false;
289 }
290 }
291
292 return true;
293 }
294
295 private static bool VisitOblique(
296 ISceneLayerCoordinates coordinates,
297 SceneLayer layer,
298 Rectangle query,
299 bool includeOverhang,
300 Func<SceneLayerTile, bool> visitor)
301 {
302 GetCornerBounds(
303 coordinates,
304 layer,
305 query,
306 includeOverhang,
307 1,
308 out int minX,
309 out int maxX,
310 out int minY,
311 out int maxY);
312
313 return VisitRowMajorClamped(layer, minX, maxX, minY, maxY, visitor);
314 }
315
316 private static bool VisitExactFallback(
317 ISceneLayerCoordinates coordinates,
318 SceneLayer layer,
319 Rectangle query,
320 bool includeOverhang,
321 Func<SceneLayerTile, bool> visitor)
322 {
323 var exact = coordinates.GetSceneLayerTilesInPixelRange(
324 layer,
325 query,
326 includeOverhang);
327
328 for (int i = 0; i < exact.Count; i++)
329 {
330 if (!visitor(exact[i]))
331 return false;
332 }
333
334 return true;
335 }
336
337 private static void GetCornerBounds(
338 ISceneLayerCoordinates coordinates,
339 SceneLayer layer,
340 Rectangle query,
341 bool includeOverhang,
342 int margin,
343 out int minX,
344 out int maxX,
345 out int minY,
346 out int maxY)
347 {
348 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
349 var ul = coordinates.GetSceneLayerCoordinatesAtPixel(
350 layer,
351 new PointF(candidate.Left, candidate.Top));
352 var ur = coordinates.GetSceneLayerCoordinatesAtPixel(
353 layer,
354 new PointF(candidate.Right, candidate.Top));
355 var ll = coordinates.GetSceneLayerCoordinatesAtPixel(
356 layer,
357 new PointF(candidate.Left, candidate.Bottom));
358 var lr = coordinates.GetSceneLayerCoordinatesAtPixel(
359 layer,
360 new PointF(candidate.Right, candidate.Bottom));
361
362 minX =
363 (int)Math.Floor(Math.Min(Math.Min(ul.X, ur.X), Math.Min(ll.X, lr.X))) -
364 margin;
365 maxX =
366 (int)Math.Ceiling(Math.Max(Math.Max(ul.X, ur.X), Math.Max(ll.X, lr.X))) +
367 margin;
368 minY =
369 (int)Math.Floor(Math.Min(Math.Min(ul.Y, ur.Y), Math.Min(ll.Y, lr.Y))) -
370 margin;
371 maxY =
372 (int)Math.Ceiling(Math.Max(Math.Max(ul.Y, ur.Y), Math.Max(ll.Y, lr.Y))) +
373 margin;
374 }
375
376 private static bool VisitRowMajorClamped(
377 SceneLayer layer,
378 int minX,
379 int maxX,
380 int minY,
381 int maxY,
382 Func<SceneLayerTile, bool> visitor)
383 {
384 if (!Clamp(layer, ref minX, ref maxX, ref minY, ref maxY))
385 return true;
386
387 return VisitRowMajor(layer, minX, maxX, minY, maxY, visitor);
388 }
389
390 private static bool VisitRowMajor(
391 SceneLayer layer,
392 int minX,
393 int maxX,
394 int minY,
395 int maxY,
396 Func<SceneLayerTile, bool> visitor)
397 {
398 if (minX > maxX || minY > maxY)
399 return true;
400
401 for (int y = minY; y <= maxY; y++)
402 {
403 for (int x = minX; x <= maxX; x++)
404 {
405 if (layer[x, y] is { } tile && !visitor(tile))
406 return false;
407 }
408 }
409
410 return true;
411 }
412
413 private static bool Clamp(
414 SceneLayer layer,
415 ref int minX,
416 ref int maxX,
417 ref int minY,
418 ref int maxY)
419 {
420 minX = Math.Max(0, minX);
421 maxX = Math.Min(layer.GridColumnCount - 1, maxX);
422 minY = Math.Max(0, minY);
423 maxY = Math.Min(layer.GridRowCount - 1, maxY);
424 return minX <= maxX && minY <= maxY;
425 }
426}
Point GridCoordinatesAbs
Gets the absolute grid coordinates of this tile within its parent scene layer as integer values.
Point OriginPx
Gets or sets the world-space pixel origin of this layer's (0,0) tile position.
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.
@ HexAxialPointedTop
Hexagonal grid using axial coordinates with pointy-topped hexes.
@ Rectangle
Particles are spawned uniformly within a rectangular area. This is the default distribution.