Search Unity

Detecting terrain texture at position

Discussion in 'Editor & General Support' started by Nima, Jun 28, 2011.

  1. Nima

    Nima

    Joined:
    May 2, 2011
    Posts:
    75
    I have a terrain with two textures painted on it. One is a grass texture and another is sand.
    I want to detect which one of these textures my player is walking on and play a corresponding looping audio clip (footstep sound).
    Does anybody know if there a way to get the terrain texture name at a specific position on the terrain?
     
  2. Nima

    Nima

    Joined:
    May 2, 2011
    Posts:
    75
    So I found the way to get all terrain textures and based on their opacity I can determine which footstep sound to play.
    Here is how I did it.

    Code (csharp):
    1.  
    2. void Start()
    3. {
    4.     mTerrainData = Terrain.activeTerrain.terrainData;
    5.     alphamapWidth = mTerrainData.alphamapWidth;
    6.     alphamapHeight = mTerrainData.alphamapHeight;
    7.  
    8.     mSplatmapData = mTerrainData.GetAlphamaps(0, 0, alphamapWidth, alphamapHeight);
    9.     mNumTextures = mSplatmapData.Length / (alphamapWidth * alphamapHeight);
    10. }
    11.  
    12. private Vector3 ConvertToSplatMapCoordinate(Vector3 playerPos)
    13. {
    14.     Vector3 vecRet = new Vector3();
    15.     Terrain ter = Terrain.activeTerrain;
    16.     Vector3 terPosition = ter.transform.position;
    17.     vecRet.x = ((playerPos.x - terPosition.x) / ter.terrainData.size.x) * ter.terrainData.alphamapWidth;
    18.     vecRet.z = ((playerPos.z - terPosition.z) / ter.terrainData.size.z) * ter.terrainData.alphamapHeight;
    19.     return vecRet;
    20. }
    21.    
    22. void Update()
    23. {
    24.     int terrainIdx = GetActiveTerrainTextureIdx();
    25.     PlayFootStepSound(terrainIdx);
    26. }
    27.  
    28. int GetActiveTerrainTextureIdx()
    29. {
    30.     Vector3 playerPos = PlayerController.Instance.position;
    31.     Vector3 TerrainCord = ConvertToSplatMapCoordinate(playerPos);
    32.     int ret = 0;
    33.     float comp = 0f;
    34.     for (int i = 0; i < mNumTextures; i++)
    35.     {
    36.         if (comp < mSplatmapData[(int)TerrainCord.z, (int)TerrainCord.x, i])
    37.             ret = i;
    38.     }
    39.     return ret;
    40. }
    41.  
     
  3. matisse_ahahah

    matisse_ahahah

    Joined:
    Oct 31, 2018
    Posts:
    1
    can you put in the start of the code(like variables and that)
     
  4. Cleo_willo876

    Cleo_willo876

    Joined:
    May 18, 2017
    Posts:
    10
    Umm... So what about the variables and other stuff like that to initialize the code?(Weak programmer, need help)
     
  5. ExDeaDguY

    ExDeaDguY

    Joined:
    Aug 25, 2009
    Posts:
    503
    I cleaned up the code a little bit. Here is the full script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TerrainManager : MonoBehaviour
    6. {
    7.     public TerrainData mTerrainData;
    8.     public int alphamapWidth;
    9.     public int alphamapHeight;
    10.  
    11.     public float[,,] mSplatmapData;
    12.     public int mNumTextures;
    13.  
    14.     void Start()
    15.     {
    16.         GetTerrainProps();
    17.     }
    18.  
    19.     private void GetTerrainProps() {
    20.         mTerrainData = Terrain.activeTerrain.terrainData;
    21.         alphamapWidth = mTerrainData.alphamapWidth;
    22.         alphamapHeight = mTerrainData.alphamapHeight;
    23.  
    24.         mSplatmapData = mTerrainData.GetAlphamaps(0, 0, alphamapWidth, alphamapHeight);
    25.         mNumTextures = mSplatmapData.Length / (alphamapWidth * alphamapHeight);
    26.     }
    27.  
    28.     private Vector3 ConvertToSplatMapCoordinate(Vector3 playerPos)
    29.     {
    30.         Vector3 vecRet = new Vector3();
    31.         Terrain ter = Terrain.activeTerrain;
    32.         Vector3 terPosition = ter.transform.position;
    33.         vecRet.x = ((playerPos.x - terPosition.x) / ter.terrainData.size.x) * ter.terrainData.alphamapWidth;
    34.         vecRet.z = ((playerPos.z - terPosition.z) / ter.terrainData.size.z) * ter.terrainData.alphamapHeight;
    35.         return vecRet;
    36.     }
    37.  
    38.     private int GetActiveTerrainTextureIdx(Vector3 pos)
    39.     {
    40.         Vector3 TerrainCord = ConvertToSplatMapCoordinate(pos);
    41.         int ret = 0;
    42.         float comp = 0f;
    43.         for (int i = 0; i < mNumTextures; i++)
    44.         {
    45.             if (comp < mSplatmapData[(int)TerrainCord.z, (int)TerrainCord.x, i])
    46.                 ret = i;
    47.         }
    48.         return ret;
    49.     }
    50.  
    51.     public int GetTerrainAtPosition(Vector3 pos)
    52.     {
    53.         int terrainIdx = GetActiveTerrainTextureIdx(pos);
    54.         return terrainIdx;
    55.     }
    56. }
    57.  
     
  6. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    268
  7. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    Hey, is there a fix for these two lines or is 2019.3 just not compatible with "ContainsIndex"? Do we need to find these indices the long way?
    Code (CSharp):
    1.  if (!CachedTerrainAlphamapData.ContainsIndex(alphamapCoordinates.x, dimension: 1))
    2.             return -1;
    3.  
    4.         if (!CachedTerrainAlphamapData.ContainsIndex(alphamapCoordinates.z, dimension: 0))
    5.             return -1;
     
    Last edited: Mar 29, 2021
  8. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    268
    ContainsIndex is a custom extension method from JUU. The code looks like this:

    Code (CSharp):
    1.  
    2. public static bool ContainsIndex(this Array array, int index, int dimension)
    3. {
    4.     if (index < 0)
    5.         return false;
    6.  
    7.     return index < array.GetLength(dimension);
    8. }
    9.  
    If you stick that in a static class, you'll be able to use the ContainsIndex function and TerrainTextureDetector should compile. You can also install the full JUU library, but that's probably overkill if you only want the one thing from it.
     
    SavedByZero likes this.
  9. homemacai

    homemacai

    Joined:
    Jul 22, 2020
    Posts:
    74
    Hey there! Thanks for the work here, I am getting a few errors after putting that ContainsIndex class:

    Code (CSharp):
    1. TerrainTextureDetector.cs(76,24): error CS0116: A namespace cannot directly contain members such as fields or methods
    2.  
    3. TerrainTextureDetector.cs(76,43): error CS0246: The type or namespace name 'Array' could not be found (are you missing a using directive or an assembly reference?)
    4.  
    5. Assets\1_Scripts\TerrainTextureDetector.cs(76,24): error CS1106: Extension method must be defined in a non-generic static class
    Any idea what I did wrong?
     
  10. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    268
    ContainsIndex is not a class, it is a method. It must be put inside a class. Note my instruction -- "If you stick that in a static class". Please read the documentation on extension methods. Also consider reading the error messages, which tell you exactly what's wrong and how to fix it.

    As for the CS0246, the Array class being referenced is this one, in the
    System
    namespace. You'll need a
    using System;
    to fix it.
     
    homemacai likes this.
  11. MasterOX13543

    MasterOX13543

    Joined:
    May 25, 2021
    Posts:
    1
    hello so i dont get any errors but where do i set the sounds for each texture?
     
  12. homemacai

    homemacai

    Joined:
    Jul 22, 2020
    Posts:
    74
    Thanks!
     
  13. nullmoongames

    nullmoongames

    Joined:
    Dec 23, 2019
    Posts:
    10
    @cxode Very very useful script, thank you very much!
     
    cxode likes this.
  14. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    268
    Cheers, I'm glad I could help :)
     
  15. SnaiperoG

    SnaiperoG

    Joined:
    Apr 6, 2015
    Posts:
    66
    not working for terrain placed not in zero coord.
    old:
    Code (CSharp):
    1.         Vector3Int ConvertToAlphamapCoordinates(Vector3 _worldPosition)
    2.         {
    3.             Vector3 relativePosition = _worldPosition - transform.position;
    4.             // Important note: terrains cannot be rotated, so we don't have to worry about rotation
    5.  
    6.             return new Vector3Int
    7.             (
    8.                 x: Mathf.RoundToInt((relativePosition.x / ter.terrainData.size.x) * ter.terrainData.alphamapWidth),
    9.                 y: 0,
    10.                 z: Mathf.RoundToInt((relativePosition.z / ter.terrainData.size.z) * ter.terrainData.alphamapHeight)
    11.             );
    12.         }
    need to add into this method substract oper for x and z. rel pos - your ter pos
    new:
    Code (CSharp):
    1.         Vector3Int ConvertToAlphamapCoordinates(Vector3 _worldPosition)
    2.         {
    3.             Vector3 relativePosition = _worldPosition - transform.position;
    4.             // Important note: terrains cannot be rotated, so we don't have to worry about rotation
    5.  
    6.             return new Vector3Int
    7.             (
    8.                 x: Mathf.RoundToInt(((relativePosition.x - terTr.position.x) / ter.terrainData.size.x) * ter.terrainData.alphamapWidth),
    9.                 y: 0,
    10.                 z: Mathf.RoundToInt(((relativePosition.z - terTr.position.z) / ter.terrainData.size.z) * ter.terrainData.alphamapHeight)
    11.             );
    12.         }