Files
DTrierFlood_New/Assets/FloodSWE/Scripts/Preprocess/DemResampler.cs

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;
}
}
}
}