How to translate WORLD coordinates to TERRAIN coordinates?

Recently I've begun messing around with altering the terrain in real-time. One of the hiccups I've run into is that world coordinates are not the same as terrain coordinates (obviously). How can I take a position in the world and translate it to the nearest point on the terrain that I can then alter the height of? I hope my question is clear enough. Any advice is appreciated!

First, subtract the terrain gameobject's world position, then divide by your terrain's size (which you can access in terrainData.size). This will give you a position with values somewhere in the range of zero to one.

You can then multiply this by your heightmap resolution (using the terrainData heightmapWidth and heightmapHeight variables), and then convert to integers to find the correct heightmap array position to feed into the GetHeights and SetHeights functions.

The terrain is a bidimensional array of float numbers between 0 and 1, where 0 is the lowest height and 1 is the highest. So 0.5 will be the half height defined in your Terrain Height variable. If you have defined a height of 300, 0.5 will be 150 height in world coordinates, 0.6 will be 180, and so on.

This way an array like this:

0, 0.5, 0

0.5, 1, 0.5

0, 0.5, 0

would make a romboid pyramid out of your terrain independent of your terrain object dimensions in world coordinates. Unity developers called heightmapWidth and heightmapHeight variables in heightmap resolution but heightmapHeight should be heightmapLength as they refer to the numbers of points in bidimensional array, and height is actually the value of each float on that array. Anyway in our example both variables have a value of 3, because our terrain has 3 x 3 values for heights. That is a very bad resolution but is enough to explain and understand. The higher resolution the most height points you will have in your terrain, the most precise will be your terrain contour and the larger will be the file.

Now imagine you raycast on the top of this pyramid knowing the gameobject terrain is 500 x 500 in world coordinates, you will have a point of collision in world coordinates x=250 z=250, assuming the terrain world position is Vector3(0, 0, 0). Then you can calculate the relation in world coordinates and pass it to terrain coordinates or viceversa. To know wich point of the terrain you have to edit:

//% of terrain x = (x point of raycast collision - x position of your terrain object) / x total width of terrain
//same with z

// raycast object is called ray

Terrain ter = GetComponent<Terrain>(); // assuming this script is attached to the terrain
float relativeHitTerX = (ray.point.x  - transform.position.x) / ter.terrainData.size.x;
float relativeHitTerZ = (ray.point.z - transform.position.z) / ter.terrainData.size.z;

// you have a number between 0 and 1 where your terrain has been hit (multiplied by 100 it would be the percentage of width and length)

relativeTerCoordX = ter.terrainData.heightmapWidth * relativeHitTerX;
relativeTerCoordZ = ter.terrainData.heightmapHeight * relativeHitTerZ;

// now you have the relative point of your terrain, but you need to floor this because the number of points on terrain are integer

int hitPointTerX = Mathf.floorToInt(relativeTerCoordX);
int hitPointTerZ = Mathf.floorToInt(relativeTerCoordZ);

//Now you can use this point to edit it using SetHeights

float[,] heights = ter.terrainData.GetHeights(); 
heights[hitPointTerx, hitPointTerZ] = heights[hitPointTerx, hitPointTerZ] - (10/ter.terrainData.size.y)
ter.terrainData.SetHeights(0, 0, heights);

// this will lower that terrain point 10% of max height of that terrain object, if you want a "crater" effect you can get and edit the points surrounding your impact point and substract a little less, like (7/ter.terrainData.size.y) for example

Check out this syntax, they can have errors as I didn’t write them in editor

You could also do a terrain.collider.Raycast:

` RaycastHit intersection = new RaycastHit();

Ray originUp = new Ray(new Vector3(0,0,0), Vector3.up);

terrain.collider.Raycast(originUp, out intersection, 100000.0f);

if (intersection.collider != null)

{
    ball.transform.position = intersection.point;

    ball.rigidbody.position = intersection.point;

}

`

private Vector3 ConvertWordCor2TerrCor(Vector3 wordCor)
{
    Vector3 vecRet = new Vector3();
    Terrain ter = Terrain.activeTerrain;
    Vector3 terPosition =  ter.transform.position;
    vecRet.x = ((wordCor.x - terPosition.x) / ter.terrainData.size.x) * ter.terrainData.alphamapWidth;
    vecRet.z = ((wordCor.y - terPosition.z) / ter.terrainData.size.z) * ter.terrainData.alphamapHeight;
    return vecRet;
}

Is this right? or i should change it like this: vecRet.x *= ((wordCor.x - terPosition.x) / ter.terrainData.size.x) * ter.terrainData.alphamapWidth; vecRet.z *= ((wordCor.y - terPosition.z) / ter.terrainData.size.z) * ter.terrainData.alphamapHeight;

draws Gizmos using TerrainData on Terrain

void OnDrawGizmos()
{
    Terrain terrain = GetComponent<Terrain>();
    Vector3 terrainSize = terrain.terrainData.size;

    for (int i = 0; i < terrain.terrainData.heightmapResolution; i += 1) // use steps of more than 1 for smoother interaction in editor
    {
        for (int k = 0; k < terrain.terrainData.heightmapResolution; k += 1)
        {
            
            Vector3 pivot = new Vector3(k, terrain.terrainData.GetHeight(k , i ), i);

            float x = pivot.x / (terrainSize.x);
            float z = pivot.z / (terrainSize.z );
            Vector3 interpolatedNormal = terrain.terrainData.GetInterpolatedNormal(x, z);
            
            GUI.color = Color.blue;
            pivot  = terrain.transform.position+ new Vector3(pivot.x / terrain.terrainData.heightmapResolution * terrainSize.x, pivot.y, pivot.z / terrain.terrainData.heightmapResolution * terrainSize.y);
            Gizmos.DrawSphere(pivot, 0.2f);
           Gizmos.color = Color.cyan;
            Gizmos.DrawRay(pivot, interpolatedNormal * rayScale);
        }
    }
}