using System; using System.Collections.Generic; using System.Globalization; using System.IO; using UnityEngine; namespace FloodSWE.IO { public sealed class SweTileLoader : MonoBehaviour { public enum SourceMode { Resources = 0, StreamingAssets = 1, AbsolutePath = 2, } [Header("Tile Index")] public TextAsset tileIndexCsv; public SourceMode sourceMode = SourceMode.Resources; public string tileIndexPath = "export_swe/swe_tile_index.csv"; public string fileRoot = ""; public string resourcesRoot = "export_swe"; [Header("Runtime")] public bool cacheTextures = true; private readonly Dictionary records = new Dictionary(); private readonly Dictionary textureCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private bool indexLoaded; public bool TryLoadTile(string lod, int tileX, int tileY, out SweTileData data) { data = default; EnsureIndexLoaded(); TileKey key = new TileKey(lod, tileX, tileY); if (!records.TryGetValue(key, out TileRecord record)) { return false; } Texture2D height = LoadTexture(record.HeightPath); Texture2D porosity = LoadTexture(record.PorosityPath); Texture2D buildings = LoadTexture(record.BuildingPath); data = new SweTileData(record, height, porosity, buildings); return height != null || porosity != null || buildings != null; } public bool ApplyToSimulator(string lod, int tileX, int tileY, SweTileSimulator simulator) { if (simulator == null) { return false; } if (!TryLoadTile(lod, tileX, tileY, out SweTileData data)) { return false; } if (data.Height != null) { simulator.terrainHeight = data.Height; } if (data.Porosity != null) { simulator.porosity = data.Porosity; } return true; } private void EnsureIndexLoaded() { if (indexLoaded) { return; } records.Clear(); string text = tileIndexCsv != null ? tileIndexCsv.text : LoadTextFromPath(); if (string.IsNullOrWhiteSpace(text)) { Debug.LogWarning("SweTileLoader: tile index CSV is empty."); indexLoaded = true; return; } string[] lines = text.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); if (lines.Length <= 1) { indexLoaded = true; return; } int headerColumns = 0; for (int i = 0; i < lines.Length; i++) { string line = lines[i].Trim(); if (line.Length == 0) { continue; } string[] parts = line.Split(','); if (i == 0) { headerColumns = parts.Length; continue; } if (parts.Length < headerColumns) { continue; } string lod = parts[0].Trim(); if (!int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out int tileX)) { continue; } if (!int.TryParse(parts[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out int tileY)) { continue; } TileKey key = new TileKey(lod, tileX, tileY); records[key] = new TileRecord( lod, tileX, tileY, NormalizePath(parts[9]), NormalizePath(parts[10]), NormalizePath(parts[11]) ); } indexLoaded = true; } private string LoadTextFromPath() { if (string.IsNullOrWhiteSpace(tileIndexPath)) { return null; } string path = tileIndexPath; if (sourceMode == SourceMode.StreamingAssets) { path = Path.Combine(Application.streamingAssetsPath, tileIndexPath); } if (!File.Exists(path)) { Debug.LogWarning($"SweTileLoader: CSV not found at {path}"); return null; } return File.ReadAllText(path); } private Texture2D LoadTexture(string path) { if (string.IsNullOrWhiteSpace(path)) { return null; } if (cacheTextures && textureCache.TryGetValue(path, out Texture2D cached)) { return cached; } Texture2D texture = null; switch (sourceMode) { case SourceMode.Resources: texture = Resources.Load(ToResourcesPath(path)); break; case SourceMode.StreamingAssets: case SourceMode.AbsolutePath: texture = LoadTextureFromFile(path); break; } if (cacheTextures && texture != null) { textureCache[path] = texture; } return texture; } private string NormalizePath(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return ""; } string path = raw.Trim().Replace("\\", "/"); if (sourceMode == SourceMode.AbsolutePath) { return path; } if (sourceMode == SourceMode.StreamingAssets) { return string.IsNullOrWhiteSpace(fileRoot) ? path : Path.Combine(fileRoot, path).Replace("\\", "/"); } return path; } private string ToResourcesPath(string path) { string normalized = path.Replace("\\", "/"); if (!string.IsNullOrWhiteSpace(resourcesRoot)) { int idx = normalized.IndexOf(resourcesRoot, StringComparison.OrdinalIgnoreCase); if (idx >= 0) { normalized = normalized.Substring(idx); } } if (normalized.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)) { normalized = normalized.Substring("Assets/".Length); } if (normalized.StartsWith("Resources/", StringComparison.OrdinalIgnoreCase)) { normalized = normalized.Substring("Resources/".Length); } if (normalized.EndsWith(".exr", StringComparison.OrdinalIgnoreCase)) { normalized = normalized.Substring(0, normalized.Length - 4); } return normalized; } private Texture2D LoadTextureFromFile(string path) { string resolved = path; if (sourceMode == SourceMode.StreamingAssets) { resolved = Path.Combine(Application.streamingAssetsPath, path); } if (!File.Exists(resolved)) { Debug.LogWarning($"SweTileLoader: texture not found {resolved}"); return null; } byte[] bytes = File.ReadAllBytes(resolved); Texture2D texture = new Texture2D(2, 2, TextureFormat.RFloat, false, true); if (!ImageConversion.LoadImage(texture, bytes, false)) { Destroy(texture); Debug.LogWarning($"SweTileLoader: failed to decode {resolved}"); return null; } texture.wrapMode = TextureWrapMode.Clamp; texture.filterMode = FilterMode.Point; texture.name = Path.GetFileNameWithoutExtension(resolved); return texture; } private readonly struct TileKey : IEquatable { public readonly string Lod; public readonly int X; public readonly int Y; public TileKey(string lod, int x, int y) { Lod = lod ?? ""; X = x; Y = y; } public bool Equals(TileKey other) { return X == other.X && Y == other.Y && string.Equals(Lod, other.Lod, StringComparison.OrdinalIgnoreCase); } public override bool Equals(object obj) { return obj is TileKey other && Equals(other); } public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 31 + X; hash = hash * 31 + Y; hash = hash * 31 + StringComparer.OrdinalIgnoreCase.GetHashCode(Lod); return hash; } } } internal readonly struct TileRecord { public readonly string Lod; public readonly int X; public readonly int Y; public readonly string HeightPath; public readonly string PorosityPath; public readonly string BuildingPath; public TileRecord(string lod, int x, int y, string heightPath, string porosityPath, string buildingPath) { Lod = lod; X = x; Y = y; HeightPath = heightPath; PorosityPath = porosityPath; BuildingPath = buildingPath; } } } public readonly struct SweTileData { public readonly string Lod; public readonly int TileX; public readonly int TileY; public readonly Texture2D Height; public readonly Texture2D Porosity; public readonly Texture2D Buildings; public SweTileData( in SweTileLoader.TileRecord record, Texture2D height, Texture2D porosity, Texture2D buildings) { Lod = record.Lod; TileX = record.X; TileY = record.Y; Height = height; Porosity = porosity; Buildings = buildings; } } }