184 lines
5.0 KiB
C#
184 lines
5.0 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|