358 lines
11 KiB
C#
358 lines
11 KiB
C#
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<TileKey, TileRecord> records = new Dictionary<TileKey, TileRecord>();
|
|
private readonly Dictionary<string, Texture2D> textureCache = new Dictionary<string, Texture2D>(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<Texture2D>(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<TileKey>
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|