13internal readonly record
struct RenderTileCandidates(
14 List<SceneLayerTile> Tiles,
15 bool IsRenderOrdered);
21internal static class RenderTileQuery
23 internal static RenderTileCandidates GetCandidates(
24 ISceneLayerCoordinates coordinates,
26 Rectangle worldPixelRange,
29 var tiles =
new List<SceneLayerTile>();
42 return new(tiles, IsRenderOrdered(layer));
55 internal static bool VisitCandidates(
56 ISceneLayerCoordinates coordinates,
58 Rectangle worldPixelRange,
60 Func<SceneLayerTile, bool> visitor)
62 ArgumentNullException.ThrowIfNull(visitor);
64 return coordinates
switch
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)
82 internal static bool IsRenderOrdered(SceneLayer layer) =>
83 layer.TransformedTiles.Count == 0 ||
86 internal static Rectangle GetFixedTileCellBounds(
94 if (layer.CoordinateSystem is OrthogonalCoordinates)
103 Point anchor = layer.CoordinateSystem.GetAnchorPixelAtSceneLayerCoordinates(
107 if (layer.CoordinateSystem is IsometricRhombicCoordinates or IsometricAxialCoordinates)
108 return new Rectangle(anchor.X - width / 2, anchor.Y, width, height);
110 return new Rectangle(anchor.X, anchor.Y, width, height);
113 private static bool VisitOrthogonal(
114 ISceneLayerCoordinates coordinates,
117 bool includeOverhang,
118 Func<SceneLayerTile, bool> visitor)
120 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
121 var ul = coordinates.GetSceneLayerCoordinatesAtPixel(
123 new PointF(candidate.Left, candidate.Top));
124 var br = coordinates.GetSceneLayerCoordinatesAtPixel(
126 new PointF(candidate.Right - 1, candidate.Bottom - 1));
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);
133 return VisitRowMajor(layer, minX, maxX, minY, maxY, visitor);
136 private static bool VisitIsometricRhombic(
137 ISceneLayerCoordinates coordinates,
140 bool includeOverhang,
141 Func<SceneLayerTile, bool> visitor)
154 if (!Clamp(layer, ref minX, ref maxX, ref minY, ref maxY))
157 for (
int depth = minX + minY; depth <= maxX + maxY; depth++)
159 int xStart = Math.Max(minX, depth - maxY);
160 int xEnd = Math.Min(maxX, depth - minY);
162 for (
int x = xStart; x <= xEnd; x++)
165 if (layer[x, y] is { } tile && !visitor(tile))
173 private static bool VisitIsometricAxial(
174 ISceneLayerCoordinates coordinates,
177 bool includeOverhang,
178 Func<SceneLayerTile, bool> visitor)
191 return VisitRowMajorClamped(layer, minX, maxX, minY, maxY, visitor);
194 private static bool VisitHexPointed(
195 ISceneLayerCoordinates coordinates,
198 bool includeOverhang,
199 Func<SceneLayerTile, bool> visitor)
201 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
205 int minRow = Math.Max(
207 (
int)Math.Floor((candidate.Top + layer.
OriginPx.Y) / (height * 0.75f)) - 2);
208 int maxRow = Math.Min(
210 (
int)Math.Ceiling((candidate.Bottom + layer.
OriginPx.Y) / (height * 0.75f)) + 2);
215 for (
int row = minRow; row <= maxRow; row++)
217 int xOffset = (row & 1) == 0 ? 0 : width / 2;
218 int minCol = Math.Max(
221 (candidate.Left + layer.
OriginPx.X - xOffset) / (
float)width) - 2);
222 int maxCol = Math.Min(
225 (candidate.Right + layer.
OriginPx.X - xOffset) / (
float)width) + 2);
227 for (
int col = minCol; col <= maxCol; col++)
229 if (layer[col, row] is { } tile && !visitor(tile))
237 private static bool VisitHexFlat(
238 ISceneLayerCoordinates coordinates,
241 bool includeOverhang,
242 Func<SceneLayerTile, bool> visitor)
244 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
247 int halfHeight = height / 2;
249 int minCol = Math.Max(
251 (
int)Math.Floor((candidate.Left + layer.
OriginPx.X) / (width * 0.75f)) - 2);
252 int maxCol = Math.Min(
254 (
int)Math.Ceiling((candidate.Right + layer.
OriginPx.X) / (width * 0.75f)) + 2);
260 (int)Math.Floor((candidate.Top + layer.
OriginPx.Y) / (
float)height) - 2;
262 (int)Math.Ceiling((candidate.Bottom + layer.
OriginPx.Y) / (
float)height) + 2;
265 (candidate.Top + layer.
OriginPx.Y - halfHeight) / (
float)height) - 2;
268 (candidate.Bottom + layer.
OriginPx.Y - halfHeight) / (
float)height) + 2;
270 int minRow = Math.Max(0, Math.Min(minEven, minOdd));
271 int maxRow = Math.Min(layer.
GridRowCount - 1, Math.Max(maxEven, maxOdd));
275 for (
int depth = minRow * 2; depth <= maxRow * 2 + 1; depth++)
277 for (
int col = minCol; col <= maxCol; col++)
279 int numerator = depth - (col & 1);
280 if ((numerator & 1) != 0)
283 int row = numerator / 2;
284 if (row < minRow || row > maxRow)
287 if (layer[col, row] is { } tile && !visitor(tile))
295 private static bool VisitOblique(
296 ISceneLayerCoordinates coordinates,
299 bool includeOverhang,
300 Func<SceneLayerTile, bool> visitor)
313 return VisitRowMajorClamped(layer, minX, maxX, minY, maxY, visitor);
316 private static bool VisitExactFallback(
317 ISceneLayerCoordinates coordinates,
320 bool includeOverhang,
321 Func<SceneLayerTile, bool> visitor)
323 var exact = coordinates.GetSceneLayerTilesInPixelRange(
328 for (
int i = 0; i < exact.Count; i++)
330 if (!visitor(exact[i]))
337 private static void GetCornerBounds(
338 ISceneLayerCoordinates coordinates,
341 bool includeOverhang,
348 var candidate = TileBounds.GetTransformedTileCandidateRange(layer, query, includeOverhang);
349 var ul = coordinates.GetSceneLayerCoordinatesAtPixel(
351 new PointF(candidate.Left, candidate.Top));
352 var ur = coordinates.GetSceneLayerCoordinatesAtPixel(
354 new PointF(candidate.Right, candidate.Top));
355 var ll = coordinates.GetSceneLayerCoordinatesAtPixel(
357 new PointF(candidate.Left, candidate.Bottom));
358 var lr = coordinates.GetSceneLayerCoordinatesAtPixel(
360 new PointF(candidate.Right, candidate.Bottom));
363 (int)Math.Floor(Math.Min(Math.Min(ul.X, ur.X), Math.Min(ll.X, lr.X))) -
366 (int)Math.Ceiling(Math.Max(Math.Max(ul.X, ur.X), Math.Max(ll.X, lr.X))) +
369 (int)Math.Floor(Math.Min(Math.Min(ul.Y, ur.Y), Math.Min(ll.Y, lr.Y))) -
372 (int)Math.Ceiling(Math.Max(Math.Max(ul.Y, ur.Y), Math.Max(ll.Y, lr.Y))) +
376 private static bool VisitRowMajorClamped(
382 Func<SceneLayerTile, bool> visitor)
384 if (!Clamp(layer, ref minX, ref maxX, ref minY, ref maxY))
387 return VisitRowMajor(layer, minX, maxX, minY, maxY, visitor);
390 private static bool VisitRowMajor(
396 Func<SceneLayerTile, bool> visitor)
398 if (minX > maxX || minY > maxY)
401 for (
int y = minY; y <= maxY; y++)
403 for (
int x = minX; x <= maxX; x++)
405 if (layer[x, y] is { } tile && !visitor(tile))
413 private static bool Clamp(
420 minX = Math.Max(0, minX);
422 minY = Math.Max(0, minY);
424 return minX <= maxX && minY <= maxY;
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.