Question

I'm writing a game in Visual Studio 2010, using the XNA 4.0 framework. I have a 3D terrain model generated from a height map. What I'm trying to accomplish is to tint this model in a given radius around a certain point, the end goal being to display to the player the radius in which a unit can move in a given turn. The method I'm using to draw the model at the moment is this:

void DrawModel(Model model, Matrix worldMatrix)
    {
        Matrix[] boneTransforms = new Matrix[model.Bones.Count];
        model.CopyAbsoluteBoneTransformsTo(boneTransforms);

        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.World = boneTransforms[mesh.ParentBone.Index] * worldMatrix;
                effect.View = camera.viewMatrix;
                effect.Projection = camera.projectionMatrix;


                effect.EnableDefaultLighting();
                effect.EmissiveColor = Color.Green.ToVector3();
                effect.PreferPerPixelLighting = true;

                // Set the fog to match the black background color
                effect.FogEnabled = true;
                effect.FogColor = Color.CornflowerBlue.ToVector3();
                effect.FogStart = 1000;
                effect.FogEnd = 3200;
            }

            mesh.Draw();
        }
    }

Also, in case it's relevant, I followed this tutorial http://create.msdn.com/en-US/education/catalog/sample/collision_3d_heightmap to create my heightmap and terrain.

Thanks in advance for any help!

Was it helpful?

Solution

You can use a shader to achieve that...

you only would need to pass as argument the world position of the center and the radius, and let the pixel shader receive the pixel world position interpolated from the vertex shader as a texture coord... then only have to check the distance of the pixel position to the center and tint it with a color if the pixel position is in range...

OTHER TIPS

The technique you are looking for is called decaling.

You have to extract the part of the terrain, where the circle will be drawn, apply an appropriate texture to that part and draw it blending it with the terrain.

For the case of a terrain based on a uniform grid, this will look like the following:

You have the center position of the decal and its radius. Then you can determine min and max row/col in the grid, so that the cells include every drawn region. Create a new vertex buffer from these vertices. Positions can be read from the heightmap. You have to alter the texture coordinates, so the texture will be placed at the right position. Assume, the center position has coordinate (0.5, 0.5), center position + (radius, radius) has coordinate (1, 1) and so on. With that you should be able to find an equation for the texture coordinates for each vertex.

Extracted grid

In the above example, the top left red vertex has texture coordinates of about (-0.12, -0.05)

Then you have the subgrid of the terrain. Apply the decal texture to it. Set an appropriate depth bias (you have to try out some values). In most cases, a negative SlopeScaleDepthBias will work. Turn off texture coordinate wrapping in the sampler. Draw the subgrid.

Here's some VB SlimDX Code I wrote for that purpose:

Public Sub Init()
    Verts = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2) ^ 2
    Tris = (Math.Ceiling(2 * Radius / TriAngleWidth) + 1) ^ 2 * 2

    Dim Indices(Tris * 3 - 1) As Integer
    Dim curN As Integer
    Dim w As Integer
    w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2)
    For y As Integer = 0 To w - 2
        For x As Integer = 0 To w - 2
            Indices(curN) = x + y * w : curN += 1
            Indices(curN) = x + (y + 1) * w : curN += 1
            Indices(curN) = (x + 1) + (y) * w : curN += 1
            Indices(curN) = x + (y + 1) * w : curN += 1
            Indices(curN) = (x + 1) + (y + 1) * w : curN += 1
            Indices(curN) = (x + 1) + y * w : curN += 1
        Next
    Next

    VB = New Buffer(D3DDevice, New BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes))
    IB = New Buffer(D3DDevice, New DataStream(Indices, False, False), New BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4))
End Sub

Public Sub Update()
    Dim Vertex(Verts - 1) As VertexPosTexColor.Struct
    Dim curN As Integer
    Dim rad As Single 'The decal radius
    Dim height As Single
    Dim p As Vector2
    Dim yx, yz As Integer
    Dim t As Vector2 'texture coordinates
    Dim center As Vector2 'decal center
    For y As Integer = Math.Floor((center.Y - rad) / TriAngleWidth) To Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
        For x As Integer = Math.Floor((center.X - rad) / TriAngleWidth) To Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
            p.X = x * TriAngleWidth
            p.Y = y * TriAngleWidth

            yx = x : yz = y
            If yx < 0 Then yx = 0
            If yx > HeightMap.GetUpperBound(0) Then yx = HeightMap.GetUpperBound(0)
            If yz < 0 Then yz = 0
            If yz > HeightMap.GetUpperBound(1) Then yz = HeightMap.GetUpperBound(1)
            height = HeightMap(yx, yz)
            t.X = (p.X - center.X) / (2 * rad)  + 0.5
            t.Y = (p.Y - center.Y) / (2 * rad) + 0.5
            Vertex(curN) = New VertexPosTexColor.Struct With {.Position = New Vector3(p.X, hoehe, p.Y), .TexCoord = t, .Color = New Color4(1, 1, 1, 1)} : curN += 1
        Next
    Next
    Dim data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None)
    data.Data.WriteRange(Vertex)
    D3DContext.UnmapSubresource(VB, 0)
End Sub

And here's the according C# code.

public void Init()
{
    Verts = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 2, 2);
    Tris = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 1, 2) * 2;

    int[] Indices = new int[Tris * 3];
    int curN;
    int w;
    w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2);
    for(int y = 0; y <= w - 2; ++y)
    {
        for(int x = 0; x <= w - 2; ++x)
        {
            Indices[curN] = x + y * w ; curN += 1;
            Indices[curN] = x + (y + 1) * w ; curN += 1;
            Indices[curN] = (x + 1) + (y) * w ; curN += 1;
            Indices[curN] = x + (y + 1) * w ; curN += 1;
            Indices[curN] = (x + 1) + (y + 1) * w ; curN += 1;
            Indices[curN] = (x + 1) + y * w ; curN += 1;
        }
    }

    VB = new Buffer(D3DDevice, new BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes));
    IB = new Buffer(D3DDevice, new DataStream(Indices, False, False), new BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4));
}

public void Update()
{
    VertexPosTexColor.Struct[] Vertex = new VertexPosTexColor.Struct[Verts] ;
    int curN;
    float rad; //The decal radius
    float height;
    Vector2 p;
    int yx, yz;
    Vector2 t; //texture coordinates
    Vector2 center; //decal center
    for(int y = Math.Floor((center.Y - rad) / TriAngleWidth); y <= Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++y)
        for(int x = Math.Floor((center.X - rad) / TriAngleWidth); x <= Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++x)
        {
            p.X = x * TriAngleWidth;
            p.Y = y * TriAngleWidth;

            yx = x ; yz = y;
            if( yx < 0)
                yx = 0;
            if (yx > HeightMap.GetUpperBound(0))
                yx = HeightMap.GetUpperBound(0);
            if (yz < 0)
                yz = 0;
            if (yz > HeightMap.GetUpperBound(1))
                yz = HeightMap.GetUpperBound(1);
            height = HeightMap[yx, yz];
            t.X = (p.X - center.X) / (2 * rad)  + 0.5;
            t.Y = (p.Y - center.Y) / (2 * rad) + 0.5;
            Vertex[curN] = new VertexPosTexColor.Struct() {Position = new Vector3(p.X, hoehe, p.Y), TexCoord = t, Color = New Color4(1, 1, 1, 1)}; curN += 1;
        }
    }
    var data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None);
    data.Data.WriteRange(Vertex);
    D3DContext.UnmapSubresource(VB, 0);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top