using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
///
/// Creates RuleTile TilingRules by first applying sprite-segment-3x3grid analysis,
/// then converting the text patterns to neighbor configurations.
///
public static class TilemapRuleTileCreateFromSegment
{
///
/// Grid position to Unity neighbor Vector3Int mapping.
/// Index 4 (center) is not included as it's not a neighbor.
///
static readonly Dictionary k_GridIndexToPosition = new()
{
{ 0, new Vector3Int(-1, 1, 0) }, // Top-Left
{ 1, new Vector3Int(0, 1, 0) }, // Top
{ 2, new Vector3Int(1, 1, 0) }, // Top-Right
{ 3, new Vector3Int(-1, 0, 0) }, // Left
// 4 is center, skipped
{ 5, new Vector3Int(1, 0, 0) }, // Right
{ 6, new Vector3Int(-1, -1, 0) }, // Bottom-Left
{ 7, new Vector3Int(0, -1, 0) }, // Bottom
{ 8, new Vector3Int(1, -1, 0) }, // Bottom-Right
};
///
/// The 47 known tile patterns from the Common Tile Patterns table.
/// Patterns not in this set are discarded by default.
///
static readonly HashSet k_KnownPatterns = new()
{
". . . / . * . / . . .", // Fully surrounded
"X . . / . * . / . . .", // Top left corner missing
". . X / . * . / . . .", // Top right corner missing
". . . / . * . / X . .", // Bottom left corner missing
". . . / . * . / . . X", // Bottom right corner missing
"X . X / . * . / . . .", // Top corners missing
". . . / . * . / X . X", // Bottom corners missing
"X . . / . * . / X . .", // Left corners missing
". . X / . * . / . . X", // Right corners missing
"X . . / . * . / . . X", // Top Left and Bottom Right diagonal
". . X / . * . / X . .", // Top Right and Bottom Left diagonal
"X . X / . * . / X . .", // Top Left, Top Right, Bottom Left
"X . X / . * . / . . X", // Top Left, Top Right, Bottom Right
"X . . / . * . / X . X", // Top Left, Bottom Left, Bottom Right
". . X / . * . / X . X", // Top Right, Bottom Left, Bottom Right
"X . X / . * . / X . X", // Cross Section
"X X X / . * . / . . .", // Flat edge (Bottom)
". . . / . * . / X X X", // Flat edge (Top)
". . X / . * X / . . X", // Flat edge (Left)
"X . . / X * . / X . .", // Flat edge (Right)
"X X X / . * . / . . X", // L-Shape, Point Right
". . X / . * X / X . X", // L-Shape, Point Bottom
"X . . / . * . / X X X", // L-Shape, Point Left
"X . X / X * . / X . .", // L-Shape, Point Top
"X X X / . * . / X . .", // L-Shape Inverse, Point Left
"X . . / X * . / X . X", // L-Shape Inverse, Point Bottom
". . X / . * . / X X X", // L-Shape Inverse, Point Right
"X . X / . * . / . . X", // L-Shape Inverse, Point Top
"X X X / . * . / X . X", // T-Shape, face down
"X . X / . * . / X X X", // T-Shape, face top
"X . X / . * X / X . X", // T-Shape, face left
"X . X / X * . / X . X", // T-Shape, face right
"X X X / . * . / X X X", // Horizontal bridge
"X . X / X * X / X . X", // Vertical bridge
"X X X / X * . / X . .", // Corner piece, Bottom Right
"X X X / . * X / . . X", // Corner piece, Bottom Left
". . X / . * X / X X X", // Corner piece, Top Left
"X . . / X * . / X X X", // Corner piece, Top Right
"X X X / X * . / X . X", // Edge end, Bottom Right
"X X X / . * X / X . X", // Edge end, Bottom Left
"X . X / . * X / X X X", // Edge end, Top Left
"X . X / X * . / X X X", // Edge end, Top Right
"X X X / . * X / X X X", // Single isolated tile, Left
"X X X / X * . / X X X", // Single isolated tile, Right
"X . X / X * X / X X X", // Single isolated tile, Top
"X X X / X * X / X . X", // Single isolated tile, Bottom
"X X X / X * X / X X X", // Center
};
///
/// Creates a RuleTile from sprites by first running sprite-segment-3x3grid analysis.
/// This is the main entry point that implements the complete workflow.
/// Duplicate patterns are automatically removed (first sprite wins).
/// Patterns not in the known 47 Common Tile Patterns are discarded by default.
///
/// Sprites to analyze and create rules for
/// Match threshold for sprite-segment-3x3grid (0.5, 0.75, or 0.9)
/// Color tolerance for sprite-segment-3x3grid
/// If true (default), discard patterns not in Common Tile Patterns
/// Sorted list of unique TilingRules (most specific first)
public static List CreateRuleTileFromSprites(
IEnumerable sprites,
float matchThreshold = 0.75f,
float colorTolerance = 0.04f,
bool filterToKnownPatterns = true)
{
var rules = new List<(RuleTile.TilingRule rule, int specificity, string pattern)>();
var seenPatterns = new HashSet();
foreach (var sprite in sprites)
{
// Step 1: Always run sprite-segment-3x3grid first
string pattern = SpriteSegment3x3Grid.AnalyzeSpriteGrid(sprite, matchThreshold, colorTolerance);
// Step 2: Discard patterns not in the known set
if (filterToKnownPatterns && !k_KnownPatterns.Contains(pattern))
{
Debug.Log($"Sprite '{sprite.name}' skipped (unknown pattern: {pattern})");
continue;
}
// Step 3: Skip duplicate patterns (keep first sprite only)
if (seenPatterns.Contains(pattern))
continue;
seenPatterns.Add(pattern);
// Step 4: Create TilingRule from the pattern
var (rule, specificity) = CreateTilingRuleFromPattern(pattern, sprite);
rules.Add((rule, specificity, pattern));
}
// Sort by specificity (descending) - more 'This' rules first
rules.Sort((a, b) => b.specificity.CompareTo(a.specificity));
return rules.Select(r => r.rule).ToList();
}
///
/// Returns true if the pattern is one of the 47 known Common Tile Patterns.
///
public static bool IsKnownPattern(string pattern) => k_KnownPatterns.Contains(pattern);
///
/// Creates a single TilingRule from a sprite-segment-3x3grid output pattern.
///
/// Pattern string from sprite-segment-3x3grid, e.g., "X X X / X * X / . . ."
/// Sprite to assign to this rule
/// Tuple of (TilingRule, specificity count)
public static (RuleTile.TilingRule rule, int specificity) CreateTilingRuleFromPattern(string pattern, Sprite sprite)
{
var cells = ParsePattern(pattern);
var neighbors = new List();
var neighborPositions = new List();
int specificity = 0;
for (int i = 0; i < 9; i++)
{
// Skip center cell
if (i == 4)
continue;
string cell = cells[i];
// Map '.' to 'This' rule
// Map 'X' to DontCare (don't add position)
if (cell == ".")
{
neighbors.Add(RuleTile.TilingRuleOutput.Neighbor.This);
neighborPositions.Add(k_GridIndexToPosition[i]);
specificity++;
}
// 'X' is implicitly DontCare - position not added
}
var rule = new RuleTile.TilingRule
{
m_Neighbors = neighbors,
m_NeighborPositions = neighborPositions,
m_Sprites = new[] { sprite },
m_Output = RuleTile.TilingRuleOutput.OutputSprite.Single,
m_ColliderType = Tile.ColliderType.Sprite,
m_RuleTransform = RuleTile.TilingRuleOutput.Transform.Fixed,
};
return (rule, specificity);
}
///
/// Parses a sprite-segment-3x3grid output pattern into an array of 9 cell values.
///
/// Pattern string, e.g., "X X X / X * X / . . ."
/// Array of 9 cell strings in order: top-left to bottom-right
public static string[] ParsePattern(string pattern)
{
var rows = pattern.Split(new[] { " / " }, StringSplitOptions.None);
if (rows.Length != 3)
throw new ArgumentException($"Pattern must have 3 rows, got {rows.Length}: {pattern}");
var cells = new string[9];
for (int row = 0; row < 3; row++)
{
var rowCells = rows[row].Split(' ');
if (rowCells.Length != 3)
throw new ArgumentException($"Row {row} must have 3 cells, got {rowCells.Length}: {rows[row]}");
for (int col = 0; col < 3; col++)
{
cells[row * 3 + col] = rowCells[col];
}
}
return cells;
}
///
/// Applies generated rules to a RuleTile asset.
/// Sets the default sprite to the last rule's sprite (least specific / bottom-most match).
///
/// Target RuleTile to configure
/// Sorted list of TilingRules to apply
public static void ApplyRulesToTile(RuleTile ruleTile, List rules)
{
ruleTile.m_TilingRules.Clear();
ruleTile.m_TilingRules.AddRange(rules);
// Use the bottom-most (least specific) rule's sprite as the default
if (rules.Count > 0 && rules[rules.Count - 1].m_Sprites.Length > 0)
ruleTile.m_DefaultSprite = rules[rules.Count - 1].m_Sprites[0];
}
}