Heightmap From Texture - Script Converter

Hello everyone!

Soo, i was trying to convert this script, that was in JavaScript to C#

So far, i “accomplished” that, but even if the script wasn’t reporting any compile erros, it won’t work!

And by the way, this script is supposed to take a Texture2D and apply into the terrain HeightMap.

Thanks :slight_smile:

link: Original Script

My “Converted” Script:

static void ApplyHeightmap(int index)
    {

        var heightmap = heightMap[index];

        var terrain = Terrain.activeTerrain.terrainData;
        var w = heightmap.width;
        var h = heightmap.height;
        var w2 = terrain.heightmapWidth;
        var heightmapData = terrain.GetHeights(0, 0, w2, w2);
        var mapColors = heightmap.GetPixels();
        var map = new Color[w2 * w2];

        if (w2 != w || h != w)
        {
            // Resize using nearest-neighbor scaling if texture has no filtering
            if (heightmap.filterMode == FilterMode.Point)
            {
                var dx = w / w2;
                var dy = h / w2;
                for (int y = 0; y < w2; y++)
                {
                    var thisY = dy * y * w;
                    var yw = y * w2;
                    for (int x = 0; x < w2; x++)
                    {
                        map[yw + x] = mapColors[thisY + dx * x];
                    }
                }
            }
            // Otherwise resize using bilinear filtering
            else
            {
                var ratioX = 1.0f / w2 / (w - 1);
                var ratioY = 1.0f / w2 / (h - 1);
                for (int y = 0; y < w2; y++)
                {
                    var yy = Mathf.Floor(y * ratioY);
                    var y1 = yy * w;
                    var y2 = (yy + 1) * w;
                    var yw = y * w2;
                    for (int x = 0; x < w2; x++)
                    {
                        var xx = Mathf.Floor(x * ratioX);

                        int temp_integer = (int)y1;
                        temp_integer += (int)xx;
                        var bl = mapColors[temp_integer];
                        temp_integer = (int)y1;
                        temp_integer += ((int)xx) + 1;
                        var br = mapColors[temp_integer];
                        temp_integer = (int)y2;
                        temp_integer += ((int)xx);
                        var tl = mapColors[temp_integer];
                        temp_integer = (int)y2;
                        temp_integer += ((int)xx) + 1;
                        var tr = mapColors[temp_integer];

                        var xLerp = x * ratioX - xx;
                        map[yw + x] = Color.Lerp(Color.Lerp(bl, br, xLerp), Color.Lerp(tl, tr, xLerp), y * ratioY - yy);
                    }
                }
            }
        }
        else
        {
            // Use original if no resize is needed
            map = mapColors;
        }

        // Assign texture data to heightmap

        for (int y = 0; y < w2; y++)
        {
            for (int x = 0; x < w2; x++)
            {
                heightmapData[y, x] = map[y * w2 + x].grayscale;
            }
        }
        terrain.SetHeights(0, 0, heightmapData);
    }

After some fiddling I finally found the cause… It was some journey, let me tell you…
After debugging I found that it was applying same grayscale (line 77) through out the whole terrain. So obviously there was some problem with rounding.


The Java is very forgiving in this it
seems. I’ve replaced whole code to
strict type (var => int, float,
etc…), that gave me errors about
not compatible types, which needed to
be resolved.
After that I’ve replaced all your
integer shenanigans with FloorToInt
methods, which led to fixing the
FilterMode.Point path, but not
bilinear filtering.
But then… then I could’t believe my
eyes. Do you know why there is this
whole problem?

This:

var ratioX = 1.0f / w2 / (w - 1);
var ratioY = 1.0f / w2 / (h - 1);

I did’t go deep into this, but in Java the

w2 and w - 1 have priority. In C# not.

if you do this:

float ratioX = 1.0f / ((float) w2 / (w - 1));
float ratioY = 1.0f / ((float) w2 / (h - 1));

It does NOT work :smiley: it is still not enough.
You have to do this:

float ratioX = (1.0f / ((float) w2 / (w - 1)));
float ratioY = (1.0f / ((float) w2 / (h - 1)));

Well without further ado, here is the final script in C#:

using System.IO;
using UnityEditor;
using UnityEngine;

public static class HeightmapFromTexture
{
[MenuItem("Terrain/Heightmap From Texture")]
static void ApplyHeightmap()
{
    //string heightmapPath = EditorUtility.OpenFilePanel("Texture", GetFolderPath(SpecialFolder.Desktop), ".png");

    Texture2D heightmap = Selection.activeObject as Texture2D;
    if (heightmap == null)
    {
        EditorUtility.DisplayDialog("No texture selected", "Please select a texture.", "Cancel");
        return;
    }

    var terrain = Terrain.activeTerrain.terrainData;
    int w = heightmap.width;
    int h = heightmap.height;
    int w2 = terrain.heightmapWidth;
    float[,] heightmapData = terrain.GetHeights(0, 0, w2, w2);
    Color[] mapColors = heightmap.GetPixels();
    Color[] map = new Color[w2 * w2];

    if (w2 != w || h != w)
    {
        // Resize using nearest-neighbor scaling if texture has no filtering
        if (heightmap.filterMode == FilterMode.Point)
        {
            float dx = (float) w / (float) w2;
            float dy = (float) h / (float) w2;
            for (int y = 0; y < w2; y++)
            {
                if (y % 20 == 0)
                {
                    EditorUtility.DisplayProgressBar("Resize", "Calculating texture", Mathf.InverseLerp(0.0f, w2, y));
                }

                int thisY = Mathf.FloorToInt(dy * y) * w;
                int yw = y * w2;
                for (int x = 0; x < w2; x++)
                {
                    map[yw + x] = mapColors[Mathf.FloorToInt(thisY + dx * x)];
                }
            }
        }
        // Otherwise resize using bilinear filtering
        else
        {
            float ratioX = (1.0f / ((float) w2 / (w - 1)));
            float ratioY = (1.0f / ((float) w2 / (h - 1)));
            for (int y = 0; y < w2; y++)
            {
                if (y % 20 == 0)
                {
                    EditorUtility.DisplayProgressBar("Resize", "Calculating texture", Mathf.InverseLerp(0.0f, w2, y));
                }

                int yy = Mathf.FloorToInt(y * ratioY);
                int y1 = yy * w;
                int y2 = (yy + 1) * w;
                int yw = y * w2;

                for (int x = 0; x < w2; x++)
                {
                    int xx = Mathf.FloorToInt(x * ratioX);

                    Color bl = mapColors[y1 + xx];
                    Color br = mapColors[y1 + xx + 1];
                    Color tl = mapColors[y2 + xx];
                    Color tr = mapColors[y2 + xx + 1];

                    float xLerp = x * ratioX - xx;
                    map[yw + x] = Color.Lerp(Color.Lerp(bl, br, xLerp), Color.Lerp(tl, tr, xLerp), y * ratioY - (float)yy);
                }
            }
        }
        EditorUtility.ClearProgressBar();
    }
    else
    {
        // Use original if no resize is needed
        map = mapColors;
    }

    // Assign texture data to heightmap

    for (int y = 0; y < w2; y++)
    {
        for (int x = 0; x < w2; x++)
        {
            heightmapData[y, x] = map[y * w2 + x].grayscale;
        }
    }

    terrain.SetHeights(0, 0, heightmapData);
}
}

Happy coding @lesmasamuray

Love u bro <3