0
\$\begingroup\$

I have the following pieces of code:

void Update()
{
  SpawnThroughPlane();
}

And:

public void SpawnThroughPlane()
{
    if (Input.GetMouseButtonDown(0))
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (plane.GetComponent<MeshCollider>().Raycast(ray, out hit, Mathf.Infinity))
        {
            GameObject cube = Instantiate(prefabToSpawn);

            cube.transform.position = hit.point;

            Vector3 pos = cube.transform.position;
            pos.y = Terrain.activeTerrain.SampleHeight(cube.transform.position);
            cube.transform.position = pos;

            cube.transform.localScale = new Vector3(50, 50, 50);
        }
    }
}

This spawns cubes all around the terrain at position 0 on the Y-axis. But I want it to position the cubes at a height depending on the terrain elevation.

I'm not sure if I need another raycast, and how to use it for the cubes before positioning them.

I tried this but it's still not working:

public void SpawnThroughPlane()
{
    if (Input.GetMouseButtonDown(0))
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (plane.GetComponent<MeshCollider>().Raycast(ray, out hit, Mathf.Infinity))
        {
            GameObject cube = Instantiate(prefabToSpawn);
            
            cube.transform.position = new Vector3(hit.point.x,
                hit.point.y + 25, hit.point.z);
        }
    }
}
\$\endgroup\$
3
\$\begingroup\$

You don't really need the Terrain.SampleHeight() function, since hit.point already contains the information (i.e. the position on the Terrain) you're looking for.

public void SpawnThroughPlane()
{
    if (Input.GetMouseButtonDown(0))
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (plane.GetComponent<TerrainCollider>().Raycast(ray, out hit, Mathf.Infinity))
        {
            GameObject cube = Instantiate(prefabToSpawn);
            cube.transform.position = hit.point;
        }
    }
}

I used TerrainCollider instead of MeshCollider because I assumed you were working with Terrains, but the same criteria apply to Mesh colliders as well, provided some minor code edits.

The previous function places cubes on the terrain without applying an offset to hit.point, and this results in cubes penetrating the Terrain after positioning:

enter image description here enter image description here

You can solve this new problem in different ways, but the main idea is moving the cube towards a certain direction for a certain distance so that it touches the Terrain without intersecting it.

Offset-based approach

When a cube is placed, its bottom half is "inside" the Terrain but its top half is actually visible. Then, we can move it upwards for half its height with respect to the hit.point. To do so, just add an offset value when setting the cube.transform.position:

...
cube.transform.position = hit.point + 0.5f * Vector3.up;
...

We multiply 0.5 to a vector to define a distance along a given direction in space. Rather than hard-coding values, you can also compute object heights programmatically by using Mesh.bounds and then Vector3.Scale() to account for scaling as well.

This small tweak improves the final result, although polygons still penetrate each other when slopes are steep:

enter image description here enter image description here

You could even use hit.normal in place of Vector3.up and apply the offset along a direction perpendicular to the hit surface. Rotating the cubes will look nice as well (I don't know if this is what you're looking for though):

enter image description here enter image description here

Intersection-based approach

Instead of positioning the cubes and adjust their position, you may use a different approach:

  • Raycast onto the Terrain, and find out the hit.point of interest
  • Assume a "safe" height from the terrain, e.g. 10 units, and consider the new position Vector3 dropFrom = hit.point + 10f * Vector3.up;
  • Use the Physics.BoxCast function, with starting position dropFrom and direction Vector3.down (or a Ray pointing downwards).

This way, you are "dropping" a cube from atop of the Terrain and casting a geometric proxy downwards until a collision occurs. The cast results in a position a cube can be placed in without intersecting the Terrain:

enter image description here

\$\endgroup\$
2
\$\begingroup\$

SampleHeight method returns a value relative to the terrain space, more details can be found here

try this:

pos.y = Terrain.activeTerrain.SampleHeight(cube.transform.position) + Terrain.activeTerrain.transform.position.y;
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.