- Home /
[SOLVED] Unity 5 - Runtime Terrain Deformation Collider Bug
It seems that to properly update a terrain and its collider in Unity 5 you have to save the scene. Similar to this issue: http://answers.unity3d.com/questions/915305/terrain-collider-bug-in-unity-5.html
The problem is that we're trying to do runtime deformation of the terrain and it properly updates it visually, but the colliders do not change. We set the terrain to not be static, but that had no effect.
Is there something that needs to be triggered programmatically on the terrain or collider for it to update from the TerrainData or is changing a TerrainCollider at runtime no longer supported?
[EDIT: This appears to be fixed in Unity 5.0.1f1]
Answer by StressedGamer · Mar 15, 2015 at 06:58 PM
Found a work around for anyone trying to generate the terrain once at runtime using CreateTerrainGameObject.
GameObject terrainObj = Terrain.CreateTerrainGameObject( terrainData );
terrainObj.transform.position = transform.position;
terrainObj.transform.rotation = transform.rotation;
float [,] heights = terrainData.GetHeights( 0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution );
terrainData.SetHeights( 0, 0, heights );
This prevented the physics geo from being mirrored around x/z.
Thanks for the suggestion.
You don't have to even create a new GameObject. It looks like setting the terrain heights for the whole terrain rebuilds the collider.
float [,] heights = terrain.terrainData.GetHeights( 0, 0, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution );
terrain.terrainData.SetHeights( 0, 0, heights );
The thing is that I don't want to set it for the entire terrain, just a region of it, as this is incredibly slow.
Yes! Thank you, I just added these two lines to my code and it works great! It doesn't even seem to have much performance impact (in my case at least...)
Yes i've confirmed it fixes this issue but terribly slow on bigger terrains. Is this known to unity, How do we get this issue tracked?
Answer by a.lomo · Mar 24, 2015 at 06:03 PM
I've submitted a bug report here: http://fogbugz.unity3d.com/default.asp?683393_bdjig2ks7rjle98v
Also here's the code I used for the bug report if anyone wants to try it:
public class OnClickRaiseTerrain : MonoBehaviour
{
void Awake()
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
// Create a new instance of the Terrain Data to not edit in the project
foreach( Terrain terrain in Terrain.activeTerrains )
{
TerrainData inst = (TerrainData) Object.Instantiate( terrain.terrainData );
terrain.terrainData = inst;
}
}
void Update()
{
if( Input.GetMouseButtonDown( 0 ) )
{
RaycastHit hitInfo;
if( Physics.Raycast( Camera.main.ScreenPointToRay( Camera.main.pixelRect.center ), out hitInfo ) )
{
Terrain terrain = hitInfo.collider.GetComponent<Terrain>();
if( terrain )
{
float[,] heights = new float[,]
{
{ .25f, .5f, .25f },
{ .5f, 1f, .25f },
{ .25f, .5f, .25f },
};
int x, y;
WorldToTerrainAlphaMap( terrain, hitInfo.point, out x, out y );
terrain.terrainData.SetHeights( x - 1, y - 1, heights );
}
}
}
}
private static void WorldToTerrainAlphaMap( Terrain terrain, Vector3 position, out int alphaMap_x, out int alphaMap_y )
{
alphaMap_x = (int) (((position.x - terrain.GetPosition().x) / terrain.terrainData.size.x) * terrain.terrainData.alphamapWidth);
alphaMap_y = (int) (((position.z - terrain.GetPosition().z) / terrain.terrainData.size.z) * terrain.terrainData.alphamapHeight);
}
}
I'm running into the same issue. I think te terrain engine in general is somewhat poorly documented and the terrain code is in need of a rehaul. Any terrafor$$anonymous$$g you want to do in runtime runs into problems like this (have you tried removing/adding trees eficciently..?) I hope your bugreport is handled! Should you find some other solution please let me know!
Answer by wopper999 · Apr 10, 2015 at 12:19 PM
more of a comment than an answer but the answer from a.lomo works well. i could not find the right place to put the code in the bug report code as listed above but i did apply it to a script i found at http://answers.unity3d.com/questions/11093/modifying-terrain-height-under-a-gameobject-at-run.html the answer from Jari Kaiji is the specific one. in that script they already have heights as a float so all you have to put in is the
terrain.terrainData.GetHeights( 0, 0, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution ); terrain.terrainData.SetHeights( 0, 0, heights );
and i placed that after the alpha map update around line 172. i also changed the terrain to myTerrain since its a float at the top (makes it easy so whatever your terrain name is it gets attached to the myTerrain calls and that is the way it is done in the code to keep it consistent. just as a warning though if you use any other terrain modifying script as a test you will need to place the terrain back in the myterrain in the terrain inspector. below is that code modified. as stated in the other article place this on the terrain. make sure to click the test with mouse.
using UnityEngine;
using System.Collections;
public class RaiseLowerTerrain : MonoBehaviour
{
public bool TestWithMouse = false;
public Terrain myTerrain;
public int SmoothArea;
private int xResolution;
private int zResolution;
private float[,] heights;
private float[,] heightMapBackup;
protected const float DEPTH_METER_CONVERT=0.05f;
protected const float TEXTURE_SIZE_MULTIPLIER = 1.25f;
public int DeformationTextureNum = 1;
protected int alphaMapWidth;
protected int alphaMapHeight;
protected int numOfAlphaLayers;
private float[, ,] alphaMapBackup;
void Start()
{
xResolution = myTerrain.terrainData.heightmapWidth;
zResolution = myTerrain.terrainData.heightmapHeight;
alphaMapWidth = myTerrain.terrainData.alphamapWidth;
alphaMapHeight = myTerrain.terrainData.alphamapHeight;
numOfAlphaLayers = myTerrain.terrainData.alphamapLayers;
if (Debug.isDebugBuild)
{
heights = myTerrain.terrainData.GetHeights (0, 0, xResolution, zResolution);
heightMapBackup = myTerrain.terrainData.GetHeights(0, 0, xResolution, zResolution);
alphaMapBackup = myTerrain.terrainData.GetAlphamaps(0, 0, alphaMapWidth, alphaMapHeight);
}
}
void OnApplicationQuit()
{
if (Debug.isDebugBuild)
{
myTerrain.terrainData.SetHeights(0, 0, heightMapBackup);
myTerrain.terrainData.SetAlphamaps(0, 0, alphaMapBackup);
}
}
void Update()
{
// This is just for testing with mouse!
// Point mouse to the Terrain. Left mouse button
// raises and right mouse button lowers terrain.
if (TestWithMouse == true)
{
if (Input.GetMouseButtonDown (0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, out hit))
{
// area middle point x and z, area width, area height, smoothing distance, area height adjust
raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, -0.01f);
// area middle point x and z, area size, texture ID from terrain textures
TextureDeformation (hit.point, 10 * 2f, DeformationTextureNum);
}
}
if (Input.GetMouseButtonDown (1))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, out hit))
{
// area middle point x and z, area width, area height, smoothing distance, area height adjust
raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, 0.01f);
// area middle point x and z, area size, texture ID from terrain textures
TextureDeformation (hit.point, 10 * 2f, 0);
}
}
}
}
private void raiselowerTerrainArea(Vector3 point, int lenx, int lenz, int smooth, float incdec)
{
int areax;
int areaz;
smooth += 1;
float smoothing;
int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
lenx += smooth;
lenz += smooth;
terX -= (lenx / 2);
terZ -= (lenz / 2);
if (terX < 0) terX = 0;
if (terX > xResolution) terX = xResolution;
if (terZ < 0) terZ = 0;
if (terZ > zResolution) terZ = zResolution;
float[,] heights = myTerrain.terrainData.GetHeights(terX, terZ, lenx, lenz);
float y = heights[lenx/2,lenz/2];
y += incdec;
for (smoothing=1; smoothing < smooth+1; smoothing++)
{
float multiplier = smoothing / smooth;
for (areax = (int)(smoothing/2); areax < lenx-(smoothing/2); areax++)
{
for (areaz = (int)(smoothing/2); areaz < lenz-(smoothing/2); areaz++)
{
if ((areax > -1) && (areaz > -1) && (areax < xResolution) && (areaz < zResolution))
{
heights [areax, areaz] = Mathf.Clamp((float)y*multiplier,0,1);
}
}
}
}
myTerrain.terrainData.SetHeights (terX, terZ, heights);
}
private void raiselowerTerrainPoint(Vector3 point, float incdec)
{
int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
float y = heights[terX,terZ];
y += incdec;
float[,] height = new float[1,1];
height[0,0] = Mathf.Clamp(y,0,1);
heights[terX,terZ] = Mathf.Clamp(y,0,1);
myTerrain.terrainData.SetHeights(terX, terZ, height);
}
protected void TextureDeformation(Vector3 pos, float craterSizeInMeters,int textureIDnum)
{
Vector3 alphaMapTerrainPos = GetRelativeTerrainPositionFromPos(pos, myTerrain, alphaMapWidth, alphaMapHeight);
int alphaMapCraterWidth = (int)(craterSizeInMeters * (alphaMapWidth / myTerrain.terrainData.size.x));
int alphaMapCraterLength = (int)(craterSizeInMeters * (alphaMapHeight / myTerrain.terrainData.size.z));
int alphaMapStartPosX = (int)(alphaMapTerrainPos.x - (alphaMapCraterWidth / 2));
int alphaMapStartPosZ = (int)(alphaMapTerrainPos.z - (alphaMapCraterLength/2));
float[, ,] alphas = myTerrain.terrainData.GetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphaMapCraterWidth, alphaMapCraterLength);
float circlePosX;
float circlePosY;
float distanceFromCenter;
for (int i = 0; i < alphaMapCraterLength; i++) //width
{
for (int j = 0; j < alphaMapCraterWidth; j++) //height
{
circlePosX = (j - (alphaMapCraterWidth / 2)) / (alphaMapWidth / myTerrain.terrainData.size.x);
circlePosY = (i - (alphaMapCraterLength / 2)) / (alphaMapHeight / myTerrain.terrainData.size.z);
distanceFromCenter = Mathf.Abs(Mathf.Sqrt(circlePosX * circlePosX + circlePosY * circlePosY));
if (distanceFromCenter < (craterSizeInMeters / 2.0f))
{
for (int layerCount = 0; layerCount < numOfAlphaLayers; layerCount++)
{
//could add blending here in the future
if (layerCount == textureIDnum)
{
alphas[i, j, layerCount] = 1;
}
else
{
alphas[i, j, layerCount] = 0;
}
}
}
}
}
myTerrain.terrainData.SetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphas);
// the next two lines are added to update the terrain to fix the unity 5 terrain collidor not updating
heights = myTerrain.terrainData.GetHeights( 0, 0, myTerrain.terrainData.heightmapResolution, myTerrain.terrainData.heightmapResolution );
myTerrain.terrainData.SetHeights( 0, 0, heights );
}
protected Vector3 GetNormalizedPositionRelativeToTerrain(Vector3 pos, Terrain terrain)
{
Vector3 tempCoord = (pos - terrain.gameObject.transform.position);
Vector3 coord;
coord.x = tempCoord.x / myTerrain.terrainData.size.x;
coord.y = tempCoord.y / myTerrain.terrainData.size.y;
coord.z = tempCoord.z / myTerrain.terrainData.size.z;
return coord;
}
protected Vector3 GetRelativeTerrainPositionFromPos(Vector3 pos,Terrain terrain, int mapWidth, int mapHeight)
{
Vector3 coord = GetNormalizedPositionRelativeToTerrain(pos, terrain);
return new Vector3((coord.x * mapWidth), 0, (coord.z * mapHeight));
}
}
there is another piece of code at http://answers.unity3d.com/questions/658026/gradual-terrain-smoothing-from-a-flat-area-at-runt.html that i would like to see if i can add to this and hopefully smooth the terrain more than the square you get from the first script. another thing i am still trying to find is a script to get the gameobjects dimensions to make the actual raise and lowering of terrain (my use is for flattening terrain when placing but also could be for basic primitive sculpting for a game also.
Answer by Darkgaze · Mar 26, 2019 at 11:18 AM
For anybody looking for help, in v 2017, 2018.. to update the TerrainCollider you have to update the heights on the terraindata of both the terrainCollider and the terrain. Seems like there is a copy. Changing the terrain only will update the visualization but not the collider itself.
terrainCollider.terrainData.setHeights(...)
terrain.terrainData.setHeights(...)
In v2019 the terrain has been moved to GPU and the API changed, so maybe this doesn't work. Seems like there is a function to update "dirty" data.