345 lines
10 KiB
C#
345 lines
10 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace FloodSWE.Preprocess
|
|
{
|
|
public static class DemResampler
|
|
{
|
|
private const float MinRangeEpsilon = 1e-6f;
|
|
|
|
public static float[] ResampleHeights(
|
|
float[] source,
|
|
int sourceRes,
|
|
int targetRes,
|
|
float flatHeightMeters,
|
|
out float sourceMin,
|
|
out float sourceMax,
|
|
bool preserveMinMax = true)
|
|
{
|
|
return ResampleHeightsInternal(
|
|
source,
|
|
sourceRes,
|
|
sourceRes,
|
|
targetRes,
|
|
flatHeightMeters,
|
|
out sourceMin,
|
|
out sourceMax,
|
|
preserveMinMax);
|
|
}
|
|
|
|
public static float[] ResampleHeights(
|
|
Texture2D source,
|
|
int targetRes,
|
|
float flatHeightMeters,
|
|
out float sourceMin,
|
|
out float sourceMax,
|
|
bool preserveMinMax = true)
|
|
{
|
|
if (source == null)
|
|
{
|
|
sourceMin = flatHeightMeters;
|
|
sourceMax = flatHeightMeters;
|
|
return CreateFlatArray(targetRes, flatHeightMeters);
|
|
}
|
|
|
|
float[] data;
|
|
try
|
|
{
|
|
data = ExtractRedChannel(source);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"DemResampler: failed to read texture {source.name}. {ex.Message}");
|
|
sourceMin = flatHeightMeters;
|
|
sourceMax = flatHeightMeters;
|
|
return CreateFlatArray(targetRes, flatHeightMeters);
|
|
}
|
|
|
|
return ResampleHeightsInternal(
|
|
data,
|
|
source.width,
|
|
source.height,
|
|
targetRes,
|
|
flatHeightMeters,
|
|
out sourceMin,
|
|
out sourceMax,
|
|
preserveMinMax);
|
|
}
|
|
|
|
public static float[] ResampleScalar(
|
|
float[] source,
|
|
int sourceRes,
|
|
int targetRes,
|
|
float flatValueMeters,
|
|
bool clampNonNegative = false)
|
|
{
|
|
float sourceMin;
|
|
float sourceMax;
|
|
float[] result = ResampleHeightsInternal(
|
|
source,
|
|
sourceRes,
|
|
sourceRes,
|
|
targetRes,
|
|
flatValueMeters,
|
|
out sourceMin,
|
|
out sourceMax,
|
|
false);
|
|
|
|
if (clampNonNegative)
|
|
{
|
|
ClampMin(result, 0.0f);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static byte[] BuildPorosityFromBuildings(
|
|
float[] buildingHeights,
|
|
float thresholdMeters = 0.1f,
|
|
byte solidValue = 0,
|
|
byte emptyValue = 255)
|
|
{
|
|
if (buildingHeights == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int length = buildingHeights.Length;
|
|
byte[] porosity = new byte[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
porosity[i] = buildingHeights[i] > thresholdMeters ? solidValue : emptyValue;
|
|
}
|
|
|
|
return porosity;
|
|
}
|
|
|
|
public static byte[] BuildDefaultPorosity(int targetRes, byte value = 255)
|
|
{
|
|
int length = targetRes * targetRes;
|
|
byte[] porosity = new byte[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
porosity[i] = value;
|
|
}
|
|
|
|
return porosity;
|
|
}
|
|
|
|
public static TileStaticData BuildTileStaticData(
|
|
float[] heightSource,
|
|
int heightSourceRes,
|
|
int targetRes,
|
|
float tileSizeMeters,
|
|
float flatHeightMeters,
|
|
float[] buildingHeightSource = null,
|
|
int buildingSourceRes = 0,
|
|
float buildingFlatHeightMeters = 0.0f,
|
|
bool preserveHeightMinMax = true,
|
|
float buildingThresholdMeters = 0.1f)
|
|
{
|
|
float sourceMin;
|
|
float sourceMax;
|
|
float[] heights = ResampleHeights(
|
|
heightSource,
|
|
heightSourceRes,
|
|
targetRes,
|
|
flatHeightMeters,
|
|
out sourceMin,
|
|
out sourceMax,
|
|
preserveHeightMinMax);
|
|
|
|
float[] buildingHeights = null;
|
|
byte[] porosity = null;
|
|
if (buildingHeightSource != null && buildingSourceRes > 0)
|
|
{
|
|
buildingHeights = ResampleScalar(
|
|
buildingHeightSource,
|
|
buildingSourceRes,
|
|
targetRes,
|
|
buildingFlatHeightMeters,
|
|
true);
|
|
porosity = BuildPorosityFromBuildings(buildingHeights, buildingThresholdMeters);
|
|
}
|
|
|
|
return new TileStaticData(
|
|
targetRes,
|
|
tileSizeMeters,
|
|
heights,
|
|
sourceMin,
|
|
sourceMax,
|
|
buildingHeights,
|
|
porosity);
|
|
}
|
|
|
|
private static float[] ResampleHeightsInternal(
|
|
float[] source,
|
|
int sourceWidth,
|
|
int sourceHeight,
|
|
int targetRes,
|
|
float flatHeightMeters,
|
|
out float sourceMin,
|
|
out float sourceMax,
|
|
bool preserveMinMax)
|
|
{
|
|
targetRes = Mathf.Max(1, targetRes);
|
|
if (source == null || source.Length == 0 || sourceWidth <= 0 || sourceHeight <= 0)
|
|
{
|
|
sourceMin = flatHeightMeters;
|
|
sourceMax = flatHeightMeters;
|
|
return CreateFlatArray(targetRes, flatHeightMeters);
|
|
}
|
|
|
|
if (source.Length != sourceWidth * sourceHeight)
|
|
{
|
|
Debug.LogWarning(
|
|
$"DemResampler: source length {source.Length} does not match {sourceWidth}x{sourceHeight}. Using flat DEM.");
|
|
sourceMin = flatHeightMeters;
|
|
sourceMax = flatHeightMeters;
|
|
return CreateFlatArray(targetRes, flatHeightMeters);
|
|
}
|
|
|
|
ComputeMinMax(source, out sourceMin, out sourceMax);
|
|
|
|
if (targetRes == sourceWidth && targetRes == sourceHeight)
|
|
{
|
|
float[] copy = new float[source.Length];
|
|
Array.Copy(source, copy, source.Length);
|
|
if (preserveMinMax)
|
|
{
|
|
NormalizeToMinMax(copy, sourceMin, sourceMax);
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
float[] output = new float[targetRes * targetRes];
|
|
float invTarget = targetRes == 1 ? 0.0f : 1.0f / (targetRes - 1);
|
|
for (int y = 0; y < targetRes; y++)
|
|
{
|
|
float v = targetRes == 1 ? 0.5f : y * invTarget;
|
|
float fy = v * (sourceHeight - 1);
|
|
for (int x = 0; x < targetRes; x++)
|
|
{
|
|
float u = targetRes == 1 ? 0.5f : x * invTarget;
|
|
float fx = u * (sourceWidth - 1);
|
|
output[y * targetRes + x] = SampleBilinear(source, sourceWidth, sourceHeight, fx, fy);
|
|
}
|
|
}
|
|
|
|
if (preserveMinMax)
|
|
{
|
|
NormalizeToMinMax(output, sourceMin, sourceMax);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
private static float SampleBilinear(float[] source, int width, int height, float fx, float fy)
|
|
{
|
|
if (width <= 1 && height <= 1)
|
|
{
|
|
return source[0];
|
|
}
|
|
|
|
int x0 = Mathf.Clamp(Mathf.FloorToInt(fx), 0, width - 1);
|
|
int y0 = Mathf.Clamp(Mathf.FloorToInt(fy), 0, height - 1);
|
|
int x1 = Mathf.Min(x0 + 1, width - 1);
|
|
int y1 = Mathf.Min(y0 + 1, height - 1);
|
|
|
|
float tx = fx - x0;
|
|
float ty = fy - y0;
|
|
|
|
float a = source[y0 * width + x0];
|
|
float b = source[y0 * width + x1];
|
|
float c = source[y1 * width + x0];
|
|
float d = source[y1 * width + x1];
|
|
|
|
float ab = Mathf.Lerp(a, b, tx);
|
|
float cd = Mathf.Lerp(c, d, tx);
|
|
return Mathf.Lerp(ab, cd, ty);
|
|
}
|
|
|
|
private static void NormalizeToMinMax(float[] data, float targetMin, float targetMax)
|
|
{
|
|
ComputeMinMax(data, out float min, out float max);
|
|
if (Mathf.Abs(max - min) < MinRangeEpsilon)
|
|
{
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
data[i] = targetMin;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
float scale = (targetMax - targetMin) / (max - min);
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
float value = (data[i] - min) * scale + targetMin;
|
|
data[i] = Mathf.Clamp(value, targetMin, targetMax);
|
|
}
|
|
}
|
|
|
|
private static void ClampMin(float[] data, float minValue)
|
|
{
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
if (data[i] < minValue)
|
|
{
|
|
data[i] = minValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static float[] CreateFlatArray(int targetRes, float value)
|
|
{
|
|
int length = targetRes * targetRes;
|
|
float[] output = new float[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
output[i] = value;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
private static float[] ExtractRedChannel(Texture2D texture)
|
|
{
|
|
Color[] colors = texture.GetPixels();
|
|
float[] data = new float[colors.Length];
|
|
for (int i = 0; i < colors.Length; i++)
|
|
{
|
|
data[i] = colors[i].r;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
private static void ComputeMinMax(float[] data, out float min, out float max)
|
|
{
|
|
min = float.PositiveInfinity;
|
|
max = float.NegativeInfinity;
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
float value = data[i];
|
|
if (value < min)
|
|
{
|
|
min = value;
|
|
}
|
|
|
|
if (value > max)
|
|
{
|
|
max = value;
|
|
}
|
|
}
|
|
|
|
if (float.IsInfinity(min))
|
|
{
|
|
min = 0.0f;
|
|
max = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|