using System; using System.IO; using System.IO.Compression; using FloodSWE.TileGraph; using UnityEngine; namespace FloodSWE.Streaming { [Serializable] public struct HeightmapPacket { public int FrameId; public TileId Tile; public int Resolution; public float MinHeight; public float MaxHeight; public byte[] Payload; public bool IsValid { get { return Resolution > 0 && Payload != null && Payload.Length > 0; } } public int SampleCount { get { return Resolution * Resolution; } } public static HeightmapPacket FromHeights(int frameId, TileId tile, int resolution, float[] heights) { if (heights == null) { throw new ArgumentNullException(nameof(heights)); } int expected = resolution * resolution; if (expected <= 0 || heights.Length != expected) { throw new ArgumentException("Height array size does not match resolution."); } float minHeight = heights[0]; float maxHeight = heights[0]; for (int i = 1; i < heights.Length; i++) { float h = heights[i]; if (h < minHeight) { minHeight = h; } else if (h > maxHeight) { maxHeight = h; } } float range = maxHeight - minHeight; if (range <= 0.0f) { range = 0.001f; maxHeight = minHeight + range; } float maxRange = 65.535f; if (range > maxRange) { maxHeight = minHeight + maxRange; range = maxRange; } ushort[] quantized = new ushort[expected]; for (int i = 0; i < heights.Length; i++) { float delta = Mathf.Clamp(heights[i] - minHeight, 0.0f, range); int mm = Mathf.RoundToInt(delta * 1000.0f); quantized[i] = (ushort)Mathf.Clamp(mm, 0, ushort.MaxValue); } byte[] payload = CompressUShorts(quantized); return new HeightmapPacket { FrameId = frameId, Tile = tile, Resolution = resolution, MinHeight = minHeight, MaxHeight = maxHeight, Payload = payload }; } public bool TryDecodeHeights(out float[] heights) { heights = null; if (!IsValid) { return false; } int expectedBytes = SampleCount * sizeof(ushort); if (!TryDecompress(Payload, expectedBytes, out byte[] raw)) { return false; } if (raw.Length < expectedBytes) { return false; } ushort[] quantized = new ushort[SampleCount]; Buffer.BlockCopy(raw, 0, quantized, 0, expectedBytes); heights = new float[SampleCount]; for (int i = 0; i < quantized.Length; i++) { heights[i] = MinHeight + (quantized[i] * 0.001f); } return true; } private static byte[] CompressUShorts(ushort[] values) { byte[] raw = new byte[values.Length * sizeof(ushort)]; Buffer.BlockCopy(values, 0, raw, 0, raw.Length); using (var output = new MemoryStream()) { using (var deflate = new DeflateStream(output, System.IO.Compression.CompressionLevel.Fastest, true)) { deflate.Write(raw, 0, raw.Length); } return output.ToArray(); } } private static bool TryDecompress(byte[] payload, int expectedBytes, out byte[] raw) { raw = null; if (payload == null || payload.Length == 0) { return false; } byte[] buffer = new byte[expectedBytes]; int offset = 0; try { using (var input = new MemoryStream(payload)) using (var deflate = new DeflateStream(input, CompressionMode.Decompress)) { while (offset < expectedBytes) { int read = deflate.Read(buffer, offset, expectedBytes - offset); if (read == 0) { break; } offset += read; } } } catch (Exception) { return false; } if (offset < expectedBytes) { return false; } raw = buffer; return true; } } }