Marching Squares

Visualising Scalar Fields : Marching Squares Part 3 : Tilemaps

June 16, 2016

Tags: , , , , , , , , , , , , , , ,

Previous : Visualising Scalar Fields : Marching Squares Part 2 : Metaballs


 

In this tutorial, we’ll be using marching squares to procedurally generate a 2D environment.

To generate the scalar field, I’ve simply used the Perlin Noise function available in the Mathf class in Unity. So if we iterate through our grid, we can get a value for each square point using:

Mathf.PerlinNoise(point.x/SCALE, point.y/SCALE) * NOISE_MULTIPLIER;

Where point.x and point.y are the coordinates for the corner being evaluated.

SCALE determines the amount to scale down the sample size of the scalar field created by the perlin noise. The image below is an example of Perlin Noise (taken from the Unity PerlinNoise API). We can use it without scaling it down, but the scalar field won’t look very realistic. I’ve put some examples of playing with the SCALE and NOISE_MULTIPLIER at the end of the post.

PerlinExample

 

 

 

 

 

 

 

 

Using this with the code written for the Contour Map tutorial we get the image below, where the black area is equal to or above the threshold value, and the white area is below.

Screen Shot 2016-06-15 at 21.53.28

 

 

 

 

 

 

 

 

 

 

NB : I’ve set the square size to be the size of the tiles, so the res is only about 9 squares across, hence the jagged edges.

So now we know how to get the scalar field, we want to start adding the tiles.

Drawing Textures onto Meshes

In the previous tutorials, we used polygons to display whichever Marching Square case we needed. In this tutorial we need to display the tile that matches the case we need.

To do this we need to do three things :

  • Add a set of vertices to the mesh that describes the outline of each tile
  • Add a set of triangles to the mesh to display the shape of the tile
  • Add a set of UVs that tell the mesh the part of the texture that each vertex points to.

The first two steps are pretty trivial; the tiles are squares, so we need to add a set of vertices and triangles that makes a square (the same as drawing case 15).

So we need four vertices, one for each corner of the square:


Vector3[] tileVertices = new Vector3[] {
<span style="visibility: hidden;">++++</span>new Vector3 (0, 0, 0),
<span style="visibility: hidden;">++++</span>new Vector3 (1, 0, 0),
<span style="visibility: hidden;">++++</span>new Vector3 (0, 1, 0),
<span style="visibility: hidden;">++++</span>new Vector3 (1, 1, 0)
};

And two triangles:


int[] tileTriangles = new int[] {
<span style="visibility: hidden;">++++</span>0, 2, 1,
<span style="visibility: hidden;">++++</span>2, 3, 1
};

For the third step, we need to tell the mesh how we want the texture to be drawn, and we do this by passing in the coordinates (or UVs) of the part of the texture we want to add. The UV coordinate is basically an XY (x,y) coordinate for the texture, renamed to UV (u,v) to prevent confusion with world coordinates. UVs are mapped from (0,0) in the bottom left corner of the texture to (1,1) in the top right corner. This is pretty handy as it allows the texture size to be changed without breaking the way the texture is displayed.

Below is a 4×4 tilemap (a tile for each marching square case, starting with case 0 at bottom left and going to case 15 at top right).

Screen Shot 2016-06-22 at 12.08.49

 

 

 

 

 

 

 

 

 

If we wanted to display the entire tilemap in one of our grid squares (with the triangles and vertices outlined above), we’d simply pass in the UV set:


wholeTextureUVs = new Vector2[]
{
<span style="visibility: hidden;">++++</span>new Vector2(0,0);
<span style="visibility: hidden;">++++</span>new Vector2(1,0);
<span style="visibility: hidden;">++++</span>new Vector2(0,1);
<span style="visibility: hidden;">++++</span>new Vector2(1,1);
}

mesh.vertices =tileVertices;
mesh.triangles = tileTriangles;
mesh.uv = wholeTextureUVs;

This maps the bottom left vertex of our square (the first index in our vertices array) to the bottom left corner of our tilemap texture, the second vertex (bottom right) to the bottom right corner of the tilemap, and so on…

If we wanted to only show the bottom left quarter of the tilemap texture, we would pass in the following UVs (the uv coordinate (0.5,0.5) is the centre of our texture).


bottomLeftQuarterUVs = new Vector2[]
{
<span style="visibility: hidden;">++++</span>new Vector2(0,0);
<span style="visibility: hidden;">++++</span>new Vector2(0.5,0);
<span style="visibility: hidden;">++++</span>new Vector2(0,0.5);
<span style="visibility: hidden;">++++</span>new Vector2(0.5,0.5);
}

Let’s say that when we iterate through our grid, the first square is Case 5.  To draw the Case 5 tile, we simply need to work out the positions of its corners within the tilemap and map those to the four vertices of our square.

Screen Shot 2016-06-22 at 12.26.01

 

 

 

 

 

 

 

 

 

As the tilemap is 4×4, and case 5 is 1 tile along and 1 tile up (outlined in yellow in the image above), we can work out that the bottom left corner of the tile (point A) has a uv of (0.25, 0.25), and the top right corner (point D) of the tile has a uv of (0.5, 0.5). So the UV set for the case 5 tile will be :


case5UV = new Vector2[]
{
<span style="visibility: hidden;">++++</span>new Vector2(0.25,0.25);
<span style="visibility: hidden;">++++</span>new Vector2(0.5,0.25);
<span style="visibility: hidden;">++++</span>new Vector2(0.25,0.5);
<span style="visibility: hidden;">++++</span>new Vector2(0.5,0.5);
}

Applying this theory for the different cases, and mapping the uvs for each grid square, we get a lovely tiled environment:

 

I’ve added an extra part to the demo that allows you to procedurally generate new parts of the environment as you move around. Using the mouse (or touch on the iphone), to move, a positional offset is passed into the PerlinNoise function that generates our scalar field.

 

Click here to see the demo (opens in new tab).


Previous : Visualising Scalar Fields : Marching Squares Part 2 : Metaballs

0 likes

Author

Your email address will not be published.