how to detect terrain type

I’m currently testing cinema 4d ( i figured i’d better buy a good 3d software for use with unity ), and i have a little question.
If you have a racing games, how do you detect that the car is on a muddy road, or on asphalt ?
Is it possible to flag the polygons ?
I was thinking of using 1 texture per terrain type, is this the way to do it ? or maybe i have to prepare it another way in the cinema 4d package ?

Thank you
patricia.

One way to do this is to cast a ray downwards using the second or fourth version of Physics.Raycast. These two functions will write to a RaycastHit, which will give you a triangle index as well as UV coordinates. The latter could be used with Texture2D.GetPixel to find out what colour the ground is.

I recommend you use either the 2-layer LayerShader or the TerrainFourLayer shader, and then check the colour on the mixing mask texture. This way your terrain types can be whatever colour you want, and you don’t have to worry about determining the terrain type by its actual colour. The other advantage of the multi-layer shaders is that you can change the scale of the tiled textures while still using the full-scale mixing mask. This allows you to have a higher-resolution terrain without using enormous textures.

1 Like

Two approaches come to mind:

  1. use a single mesh representing the whole “ground”, and draw a special texture where colors indicate the terrain type. Or draw the same information into vertex colors of that mesh. This mesh could be a simpler version of the visible mesh (i.e. it does not have to be the same as the visible mesh). Then raycast against this mesh, get back the UVs or triangle data, and read the color at that spot in your terrain-types texture (or get vertex colors from the triangle that is hit).

  2. use separate meshes for different terrain types. One mesh that is only the asphalt, one that is only the grass and so on. If joined, those meshes would form the whole “ground”. Then raycast, and depending on which mesh is hit you know the terrain type.

Welcome, patricia!
Hm, I guess it kind of depends on what you want to do, like change the way the vehicle rides over the terrain, etc… You could use a trigger at the start of each terrain, one that stretches across the road. For that you could just use a cube with a function OnTriggerEnter() script that triggers whatever event that you want to happen. Just set the box collider to IsTrigger and make the cube invisible by clicking the renderer off.

I don’t know if that helps, because there might be something else in particular that you’re trying to do.

Edit: Oops, the other answers are better! I was thinking along different lines…

Thanks for the suggestions.
Shaders are not an option, if won’t work on my video card.
I was also thinking of using different mesh, but then it’s going to be a nightmare to edit the land :slight_smile:
I’d rather just model one big land and texture it. ( maybe i’m wrong and i should use several mesh though, i’m not experienced enough )
The color idea is doable, that’s something that could work.
Thanks
patricia.

Alpha blending wont even work on your card? Ouch. I used to use an old Emac with a GeForce MX card, and I had similar issues.

The vertex colors are stored in the mesh though, so you can get them without a shader. Check the Mesh class for Mesh.colors in the scripting reference. You will have to write code that does a raycast and gets the vertex colors though, which might be a pain depending on your coding skill. If you request it, I am sure someone could help guide you in a direction for that.

An easier way would be the trigger idea that terransage suggested. What I would do is have an index for each terrain/floor type (0=rock, 3=grass, etc) then when you enter the trigger for an area, check a script on the player to see if that index is already set as the current ground cover, and if it isn’t set it to the index of this section.

Then you use that stored index to decide what sound to play. This way you could have several triggers set up to cover the same area without worrying, because if the ground type is already set correctly, nothing get’s changed.

HTH,
-Jeremy

While doing some mod work on “call of duty” i noticed, that all the texture-names contained somthing like metal@ oder rock@ and so on in their file-name.

Is it possible to check the terrain by identifying the textures file-name?

Just an idea from a non-coder.

Nope, the Material class doesn’t have a texture name property, just the usual inherited object name.

EDIT: and either way, if you use one texture for a few different terrain types (most likely as her card can’t do blending), then you are still screwed.

-Jeremy

theMaterial.mainTexture.name

In response to the original topic, if it were me I would use a splat shader on the terrain (See wiki) and then get the current UV coordinate under the car (using Raycast) and then get the color at that coordinate from the control texture. Once you have the color, you can determine which texture is strongest at that point and proceed from there.

Cheers,
-Jon

Thanks again.

Would that work with only one or also with multiple textures applied to a single object?

In the case of multiple materials, you can look at each one by getting a list via renderer.materials.

for (var mat in renderer.sharedMaterials) { Debug.Log(mat.mainTexture.name); }

-Jon

edit: Oops, got my Unity versions wrong! It’s sharedMaterials.

That helps a lot! :smile:

Cool thanks Jon, didn’t know you could get a list of all materials. But would this work with an alpha blended terrain shader? (forgive my lack of shader knowledge).

Anyway, for the vertex color idea, I just whipped up a script to do that.

It’s in C#, but pretty well abstracted. It currently has the debug run still set (to remove it, delete the Update() block)

using UnityEngine;
using System;
using System.Collections;

public class GroundType : MonoBehaviour
{
    public Transform characterTransform = null;  //if left unset, it will use the current object's transform
    public Color[] groundTypeColors = null;      //the colors we want to use to specify our ground types
    public Vector3 raycastDirection = Vector3.down;
    public float raycastDistance = 10.0F;

    private RaycastHit hit;
    private int triIdx;
    private Vector3[] vertices;
    private int[] tris;
    private Color[] colors;
    private Color p0, p1, p2;

    void Start()
    {
        if (characterTransform == null)
            characterTransform = transform;
    }

    void Update()
    {
        Debug.Log(GetGroundColor().ToString());
    }

    public int GetGroundColor()
    {
        if (Physics.Raycast(characterTransform.position, raycastDirection, out hit, raycastDistance))
        {
            //set up the mesh data
            MeshCollider meshCollider = hit.collider as MeshCollider;
            if (meshCollider == null || meshCollider.sharedMesh == null)
                return -1; //error
            Mesh mesh = meshCollider.sharedMesh;
            vertices = mesh.vertices;
            tris = mesh.triangles;
            colors = mesh.colors;

            //now check the vertex colors
            triIdx = hit.triangleIndex;
            p0 = colors[tris[triIdx * 3 + 0]];
            p1 = colors[tris[triIdx * 3 + 1]];
            p2 = colors[tris[triIdx * 3 + 2]];

            for (int i = 0, iLength = groundTypeColors.Length; i < iLength; i++)
            {
                for (int x = 0, xLength = vertices.Length; x < xLength; x++)
                {
                    //currently only checks one point of the triangle, if you need to test all 3, do the same test for p1 and p2 as well
                    if (Mathf.Approximately(p0.r, (float)groundTypeColors[i].r)  Mathf.Approximately(p0.g, (float)groundTypeColors[i].g)  Mathf.Approximately(p0.b, (float)groundTypeColors[i].b))
                    {
                        //Debug.Log(i.ToString());
                        return i;
                    }
                }
            }            
        }
        return -1;
    }
}

Put this on some object and set the following vars in the inspector:

-Character Transform = drag your character to this slot if this script is not on your character. Otherwise leave blank and the raycast will be fired from the position of the object this script is placed on.

-Ground Type Colors = is an array of all the vertex colors you want to test against. Set size to the amount of colors you want to test against, and set them accordingly.

Raycast Direction = set to Vector3.down (0, -1, 0), you should be able to leave this alone.

Raycast Direction = the distance to fire the raycast. Set to 10 which should be fine, but if your character is not finding any colors, this may need to be set larger.

It will print the index of the color it found in the console at the bottom (returned values explained below)

The function GetGroundColor() returns a number which tells you what color (if any) it hit. The returned number is an index to that Ground Type Colors array we set earlier, so a returned index of 1 would be the “Element 1” color.

GetGroundColor() will return -1 if none of the colors match.

please note This currently only checks against 1 vertex of the hit triangle. If you need to make sure all 3 points have the same color, modify the code where I commented.

I am sure there is a nicer way to do it, but it works. :wink:

You should also be able to convert this to JS easy enough.

HTH,
-Jeremy

Hi, I have the same problem and found that jeremyace code does exactly what was needed. Anyway, even before actually trying jeremyace code, I’m a little concerned about speed. Doing all this stuff all frames would degrade performance? What about building a 2d array using this script at load time to “map” terrain and assign to each array value the correct terrain type? I’m just thinking about this color coded chessboard, that would reduce runtime operations to a simple lookup on the array based on actual player position.
Would like to know your opinions and many thanks to anyone.

Yes, in most cases caching is a good idea if you can afford the memory hit.

As long as you have a very quick way to map position to your lookup table, you could get the feature almost for free, after preprocessing.

-Jeremy