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