Terrain Cutout

I’m trying to learn some of the terrain tools and the terrainData classes, but I’m stuck on this problem.

I’d like to set the height of all terrain vertices that fall within meshFilter.Bounds for an object (shown in attached diagram).

The below script runs correctly, but what I’m having trouble with is finding the x,y values to use for the setHeight(x,y) function. I need an array of vertices that all fall within the meshFilter.Bounds and I’m not sure how to get that.

Here’s my code so far:

void Start () {

        GameObject building = GameObject.Find("building2");
        Mesh buildingMesh = building.GetComponent<MeshFilter>().mesh;
        Bounds buildingBounds = buildingMesh.bounds;

        Terrain terrain = gameObject.GetComponent<Terrain>();
        TerrainData terrainData = terrain.terrainData;
        Collider terrainCollider = terrain.GetComponent<TerrainCollider>().collider;
        Bounds terrainBounds = terrainCollider.bounds;

        if (buildingBounds.Intersects(terrainBounds)){
            Debug.Log ("An Intersection");
            //gather all the verts in this bounding area and setHeight to zero

            float [,] vertexHeights = terrainData.GetHeights(0, 0, Mathf.CeilToInt(buildingBounds.size.x), Mathf.CeilToInt(buildingBounds.size.y));

            foreach (float item in vertexHeights){
                Debug.Log (item);

                }


        }
        else{
            Debug.Log ("No Intersection");
        }


       
    }

Tinkering with this a bit, it seems that the coordinates expected by .GetHeights() are actually integer coordinates on the terrain’s local heightmap space.

TerrainData.heightmapResolution will tell you how many across and down this heightmap is.

But you need to use other information in the terrain object as well as the terrain object’s actual world space in order to actually tell if your building’s bounding box intersects any parts of the ground you care about.

The individual cell dimensions of the terrain seem to be in the .size variable, a Vector3 which essentially scales the entire terrain.

The following script “pulses” the land vertically to demonstrate this.

using UnityEngine;
using System.Collections;

public class LivingTerrain : MonoBehaviour
{
    Terrain ter;
    Vector3 size;

    void Start()
    {
        ter = FindObjectOfType<Terrain> ();
        size = ter.terrainData.size;
    }
    void Update()
    {
        // CAUTION! this actively modifies the .size parameter of your
        // terrain data asset as it exists in your filesystem.
        // DO NOT USE IT IF YOU HAVEN'T BACKED UP YOUR TERRAIN!!
        // Always use source control to avoid asset data loss.
        // Comment out this return statement if you agree to this!
        return;

        // pulsing height function
        ter.terrainData.size = new Vector3 (
            size.x, size.y + size.y * 0.3f * Mathf.Sin (Time.time), size.z);
    }
}

Isn’t that just what this does when it returns true?

if (buildingBounds.Intersects(terrainBounds)){}

I’m stuck in determining where this happens? TerrainData.size returns the size.x and size.y for the entire dimension of the terrain, where I want just the intersection of the bounds(). What am I missing?

The following script shows you more succinctly the difference in the nature of the coordinates you are using. You need to convert your world object’s bounds into something else, the height map coordinates that the TerrainData.GetHeights() function expects.

using UnityEngine;
using System.Collections;

// make a new scene, create a new default-sized terrain, put
// this script on it, press Play to see info in console log.
//
// This reveals that TerrainData.Size is in world Dimensions, while
// the .GetHeights() function is expecting heightmap dimensions.

public class TerrainInfoDemo : MonoBehaviour
{
    void Start ()
    {
        Terrain ter = gameObject.GetComponent<Terrain> ();

        TerrainData td = ter.terrainData;

        // default terrain is 2000 x 600 x 2000 (world size of terrain)
        Debug.Log ( System.String.Format( "TerrainData.size = {0}", td.size));

        // default terrain is 513 x 513
        Debug.Log ( System.String.Format ("Heightmap width/height = {0}, {1}",
                                        td.heightmapWidth, td.heightmapHeight));

        // this statement succeeds on default terrain size
        td.GetHeights (0, 0, 513, 513);

        Debug.Log ("First one succeeded!");

        // this statement's arguments are out of bounds
        // an exception should be thrown
        td.GetHeights (0, 0, 514, 514);
    }
}

As a curiousity I took a whack at carving and molding Unity3D terrain in real time, to conform to the bottom of buildings, and I got a preliminary version going.

It’s complicated.

See the TerrainCutter in the enclosed unitypackage, and be CAREFUL. This will permanently modify your terrain heightmap data on disk, so make sure you are using source control in case there are any errors.

See notes in the script itself for more information. Basically the script traverses all renderers parented below the Terrain object, uses their MeshFilter mesh bounds box, scales and rotates that box to imitate the object’s transforms (within important limits! Read the script), and then traverses the heightmap data and modifies any affected pieces of land that either need to be raised or lowered.

There are many spaces here: world space, local object, local terrain, heightmap array, and this script laboriously transfers and translates between them, introducing rounding and/or data loss errors at each step of the way.

Good luck. I’m done with this, if it helps anyone, that’s awesome. :slight_smile:

1931480–124809–TerrainCutter.unitypackage (47.1 KB)

1 Like