6public static class TilesheetDefinitionValidator
13 public static IReadOnlyList<string> Validate(TilesheetDefinition definition,
int? imageWidth =
null,
int? imageHeight =
null)
15 ArgumentNullException.ThrowIfNull(definition);
16 var errors =
new List<string>();
17 var names =
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
18 foreach (var region
in definition.Regions ?? [])
20 if (region is
null) { errors.Add(
"Regions contains a null entry.");
continue; }
21 var label = $
"Region '{region.Name}'";
22 if (
string.IsNullOrWhiteSpace(region.Name)) errors.Add($
"{label}: name is empty.");
23 if (!names.Add(region.Name)) errors.Add($
"{label}: duplicate name.");
24 if (region.Area.Width <= 0 || region.Area.Height <= 0 || region.TileSize.Width <= 0 || region.TileSize.Height <= 0)
26 errors.Add($
"{label}: area and tile dimensions must be positive.");
29 if (region.Area.X < 0 || region.Area.Y < 0 ||
30 (imageWidth.HasValue && (
long)region.Area.X + region.Area.Width > imageWidth) ||
31 (imageHeight.HasValue && (
long)region.Area.Y + region.Area.Height > imageHeight)) errors.Add($
"{label}: area is outside the source image.");
32 if (Negative(region.TilePadding)) errors.Add($
"{label}: tile padding cannot be negative.");
33 if (region.RegionMargin.Left < 0 || region.RegionMargin.Top < 0 ||
34 region.RegionMargin.Right < -region.TilePadding.Right ||
35 region.RegionMargin.Bottom < -region.TilePadding.Bottom)
36 errors.Add($
"{label}: leading margins cannot be negative; trailing margins may only cancel trailing tile padding.");
37 if (Negative(region.Overhang)) errors.Add($
"{label}: overhang cannot be negative.");
38 var (columns, rows) = GridSize(region);
39 if (columns <= 0 || rows <= 0) errors.Add($
"{label}: layout contains no complete frames.");
40 CheckCollision(region.CollisionAdjust, region, label, errors);
41 if (!Enum.IsDefined(region.CollisionType)) errors.Add($
"{label}: unknown collision type.");
42 var coordinates =
new HashSet<(int, int)>();
43 foreach (var frame
in region.Frames ?? [])
45 if (frame is
null) { errors.Add($
"{label}: null frame metadata.");
continue; }
46 var frameLabel = $
"{label}, frame ({frame.XTile}, {frame.YTile})";
47 if (!coordinates.Add((frame.XTile, frame.YTile))) errors.Add($
"{frameLabel}: duplicate frame metadata.");
48 if (frame.XTile < 0 || frame.YTile < 0 || frame.XTile >= columns || frame.YTile >= rows) errors.Add($
"{frameLabel}: coordinates are outside the frame grid.");
49 if (frame.CollisionAdjust is { } adjust) CheckCollision(adjust, region, frameLabel, errors);
50 if (frame.CollisionType is { } type && !Enum.IsDefined(type)) errors.Add($
"{frameLabel}: unknown collision type.");
59 public static (
long Columns,
long Rows) GridSize(TilesheetRegionDefinition region)
61 long width = (long)region.TileSize.Width + region.TilePadding.Left + region.TilePadding.Right;
62 long height = (long)region.TileSize.Height + region.TilePadding.Top + region.TilePadding.Bottom;
63 return (width <= 0 ? 0 : Math.Max(0, ((
long)region.Area.Width - region.RegionMargin.Left - region.RegionMargin.Right) / width),
64 height <= 0 ? 0 : Math.Max(0, ((
long)region.Area.Height - region.RegionMargin.Top - region.RegionMargin.Bottom) / height));
67 private static bool Negative(
Spacing spacing) => spacing.Left < 0 || spacing.Right < 0 || spacing.Top < 0 || spacing.Bottom < 0;
68 private static void CheckCollision(CollisionAdjust adjust, TilesheetRegionDefinition region,
string label, List<string> errors)
72 if ((
long)adjust.
Left + adjust.
Right > region.TileSize.Width || (
long)adjust.
Top + adjust.
Bottom > region.TileSize.Height)
73 errors.Add($
"{label}: collision adjustments invert the frame's collision rectangle.");
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.
int Top
Gets or sets the number of pixels by which to inset the top edge.
int Left
Gets or sets the number of pixels by which to inset the left edge.
int Bottom
Gets or sets the number of pixels by which to inset the bottom edge.
int Right
Gets or sets the number of pixels by which to inset the right edge.