Myhijim CoreDev CEO
Posts : 66 Join date : 20120907 Location : Sydney, Australia
  4  Chunk by Chunk (Part 2)  
Alright, I am back. Yes I have been lazy since my last post, leaving it unfinished, but now I am writing the conclusion to the first chapter, the actual Voxel part of the engine. What are we covering now?Yes right, we are now covering everything to do with the actual meshes , verticies rearangement and face rendering. The chunk is not made up of 1000 individual blocks. This may get you thinking, "But the chunk size is 10x10x10". So let me get this straight, the chunk is actually one mesh, it is one object! The chunk does not contain 100's of blocks as objects, but as values, remember the last post where we set the 3dimensional array :  Code:
private var chunk:int[,,] = new int[s,s,s];
This variable "chunk" is a 10 by 10 by 10 array...... 1000 "spots". Catching on now? Thus if we take the [,,] from the code it will actualy give us the "blocks" position in the chunk in values of [x,y,z]. e.g chunk[0,0,1] would be the local position of the block [0,0,1].But really to understand you need to see the code. So here is the code decriptionNow this next piece of code is MASSIVE in the source if you looked, but don't worry, it is less daunting when you look at it in parts, it is basically repeating itself. IF you are struggling to read the code(too bunched etc) Please zoom out in your browser ("ctrl" "") Code:
function ChunkRender(chunk:int[,,]) {
var vertices :List. = new List.();
var uvs:List. = new List.();
var triangles:List. = new List.();
var vertexIndex:int; var top:int; var north:int; var east:int; var south:int; var west:int; var bottom:int;
GetComponent(MeshFilter).mesh.Clear();
for (var x = 0; x< s; x )
for (var y = 0; y< s; y )
for (var z = 0; z < s; z ) { var block = chunk[x, y, z];
if (y==s1){top = 0;}else{top = chunk[x, y 1, z];} if (y==0){bottom = 0;}else{bottom = chunk[x, y  1, z];} if (z==s1){north = 0;}else{north = chunk[x, y, z 1];} if (z==0){south = 0;}else{south = chunk[x, y, z1];} if (x==s1){east = 0;}else{east = chunk[x 1, y, z];} if (x==0){west = 0;}else{west = chunk[x1, y, z];}
// we are checking the top face of the block, so see if the top is exposed if (block == 1 && top == 0) { vertexIndex = vertices.Count; vertices.Add(new Vector3(x, y 1, z)); vertices.Add(new Vector3(x, y 1, z 1)); vertices.Add(new Vector3(x 1, y 1, z 1)); vertices.Add(new Vector3(x 1, y 1, z)); // first triangle for the block top triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2); // second triangle for the block top triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex); // add UV uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); }
if (block == 1 && north == 0) { vertexIndex = vertices.Count; vertices.Add(new Vector3(x, y, z 1)); vertices.Add(new Vector3(x 1, y, z 1)); vertices.Add(new Vector3(x 1, y 1, z 1)); vertices.Add(new Vector3(x, y 1 , z 1)); // first triangle for the block top triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2); // second triangle for the block top triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex); // add UV uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); }
if (block == 1 && east == 0) { vertexIndex = vertices.Count; vertices.Add(new Vector3(x 1, y, z)); vertices.Add(new Vector3(x 1, y 1, z)); vertices.Add(new Vector3(x 1, y 1, z 1)); vertices.Add(new Vector3(x 1, y , z 1)); // first triangle for the block top triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2); // second triangle for the block top triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex); // add UV uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); }
if (block == 1 && south == 0) { vertexIndex = vertices.Count; vertices.Add(new Vector3(x, y, z)); vertices.Add(new Vector3(x, y 1, z)); vertices.Add(new Vector3(x 1, y 1, z)); vertices.Add(new Vector3(x 1, y , z)); // first triangle for the block top triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2); // second triangle for the block top triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex); // add UV uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); }
if (block == 1 && west == 0) { vertexIndex = vertices.Count; vertices.Add(new Vector3(x, y, z 1)); vertices.Add(new Vector3(x, y 1, z 1)); vertices.Add(new Vector3(x, y 1, z)); vertices.Add(new Vector3(x, y , z)); // first triangle for the block top triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2); // second triangle for the block top triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex); // add UV uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); }
if (block == 1 && bottom == 0) { vertexIndex = vertices.Count; vertices.Add(new Vector3(x, y, z)); vertices.Add(new Vector3(x 1, y, z)); vertices.Add(new Vector3(x 1, y, z 1)); vertices.Add(new Vector3(x, y , z 1)); // first triangle for the block top triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2); // second triangle for the block top triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex); // add UV uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); } }
var mesh : Mesh = GetComponent(MeshFilter).mesh; mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.uv = uvs.ToArray(); mesh.RecalculateNormals();
GetComponent(MeshCollider).sharedMesh = null; GetComponent(MeshCollider).sharedMesh = mesh; }
That is the whole rest of the script! So lets break it DOWN! Code:
function ChunkRender(chunk:int[,,]) {
This just creates a new function ChunkRender taking a 3dimensional array that contains integers.  Code:
var vertices :List. = new List.();
Creates an empty List of Vector3's. This will contain the locations of all the points/verticies.  Code:
var uvs:List. = new List.();
Creates another empty list (Vector2) that will contain the Uvs values, relating to the textures being drawn. (Vector2, because a side is only two dimensional)  Code:
var triangles:List. = new List.();
Yet another list, this time of just integers holding the amount of triangles Code:
var vertexIndex:int;
This is used later in the code relating to the triangles variable.  Code:
var top:int; var north:int; var east:int; var south:int; var west:int; var bottom:int;
Pretty self explainitory, these are the block's surrounding blocks. Why an int? Because with our current code a block can either be a 0 or 1, 0 being air and 1 being a solid.  Code:
GetComponent(MeshFilter).mesh.Clear();
This is important as it resets the mesh to nothing, a blank canvas if you will. The following code now rebuilds the mesh!  Code:
for (var x = 0; x< s; x ) for (var y = 0; y< s; y ) for (var z = 0; z < s; z ) {
Runs 3 for loops, self explainitory once again. It runs it untill x,y,z are all equal to the chunksize (10). First loop runs ten times running the next loop 10 times and running the next loop 10 times, thus equalling our 1000 "blocks".  Code:
var block = chunk[x, y, z];
The variable block holds the current "block" values throughout the loop.  Code:
if (y==s1){top = 0;} else{top = chunk[x, y 1, z];}
if (y==0){bottom = 0;} else{bottom = chunk[x, y  1, z];}
if (z==s1){north = 0;} else{north = chunk[x, y, z 1];}
if (z==0){south = 0;} else{south = chunk[x, y, z1];}
if (x==s1){east = 0;} else{east = chunk[x 1, y, z];}
if (x==0){west = 0;} else{west = chunk[x1, y, z];}
Once again quite simple, the if part is just determining wether the block adjacent is air. Thus making it a 0. Else it will set its adjacents to the correct chunk. e.g {east = chunk[x 1, y, z];}Here the east value is plus one on the x axis Next I am going to go over one of the face checking sequences (if statements) and leave the rest for you to view in the source and figure out, as tat will expand your knowledge much further.  Code:
if (block == 1 && top == 0) {
This code just checks if the current block is NOT air(0) and if the above block IS air(1). Reason for the top needing to equal 0 is because the rest of the code is only run if the current blocks face is visible.  Code:
vertexIndex = vertices.Count; vertices.Add(new Vector3(x, y 1, z)); vertices.Add(new Vector3(x, y 1, z 1)); vertices.Add(new Vector3(x 1, y 1, z 1)); vertices.Add(new Vector3(x 1, y 1, z));
triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2);
triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex);
uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); }
A long piece of code, this just sets the boundries. I will look at this piece by piece.  Code:
vertexIndex = verticies.Count;
vertexIndex is how many array values are in the variable verticies(which is an array), basically returning the size of the array.  Code:
vertices.Add(new Vector3(x, y 1, z)); vertices.Add(new Vector3(x, y 1, z 1)); vertices.Add(new Vector3(x 1, y 1, z 1)); vertices.Add(new Vector3(x 1, y 1, z));
Now this is where the MAGIC HAPPENS! This sets where the faces need to be drawn, this is so confusing I DREW A PICTURE! (Not sure if tis 100% accurate)
This picture shows where the values line up to, seeing as we are only drawing the top face, it is the one shown.  Code:
triangles.Add(vertexIndex); triangles.Add(vertexIndex 1); triangles.Add(vertexIndex 2);
triangles.Add(vertexIndex 2); triangles.Add(vertexIndex 3); triangles.Add(vertexIndex);
Now this code will probably confuse you, you go " There are only 2 triangles in a face!", but you see the variable is a int and each triangle has 3 verticies thus the addition is done 6 times. 6/3 = 2. 2 Triangles .
The reason we add vertexIndex is because it determines where in the 100 blocks across the x and z axis, the triangle needs to be. The first 3 determine the first triangle and the second 3 the second respectively.  Code:
uvs.Add(Vector2 (0, 0)); uvs.Add(Vector2 (0, 1)); uvs.Add(Vector2 (1, 1)); uvs.Add(Vector2 (1, 0)); Completely to do with the textures, it outlines the four verticies(as a square has 4 corners) in which the texture needs to be drawn too. Here is another picture Basically it will stretch the texture to fit those points, the (0,0) of the texture will slot into the (0,0) of the face. What you need to do now?Before we continue, you need to figure out the rest of the sides, using your own knowledge or the source. This is ESSENTIAL before we continue!Hopefully, that helps you visualise the rest of the sides, I have given you 1 out of 6, try and figure out the rest of them (bottom, south,north,east,west) . Continuing on......We are on the home stretch.
All that really needs to be done now is to recreate the mesh as we have a TON of values...... but we have not done anything with them, the next code does this.  Code:
var mesh : Mesh = GetComponent(MeshFilter).mesh; mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.uv = uvs.ToArray(); mesh.RecalculateNormals();
This script applies all of our new values to the new mesh. RecalculateNormals makes sure the values are applied and that they are smooth. Why ToArray()?
This is beacuse the uv, triangles AND verticies of a mesh are in array format.  Code:
GetComponent(MeshCollider).sharedMesh = null; GetComponent(MeshCollider).sharedMesh = mesh;
}
sharedMesh is the physics collider of the mesh, we first set it to null, then reset it by making it equal to the new mesh that uses our values. Finished....... AlmostI say almost because most of you will not marvel at the awesomeness of the script, but see it as useless because you can't dig...... That's why I am writing up a bonus part right now using my own code NOT the sources, as my script suports multiple chunks. I shall post it now, and explain later This is in a seperate script attached to the camera. The camera ALSO needs a line renderer attached to it (Found in "Components/Effects")  Code:
#pragma strict
private var lineRenderer : LineRenderer; lineRenderer = Camera.main.transform.GetComponent(LineRenderer); var chunkScr : ChunkRender;
function Update () {
if (Input.GetMouseButtonUp(0)) { lineRenderer.SetPosition(0, Camera.main.transform.position); lineRenderer.SetPosition(1, Camera.main.transform.position); }
if (Input.GetMouseButton(0)) { var ray:Ray = Camera.main.ScreenPointToRay(Input.mousePosition); var hit:RaycastHit; if (Physics.Raycast(ray,hit, 100)) { var hx:int = hit.point.x  hit.transform.position.x; var hy:int = hit.point.y0.2  hit.transform.position.y; var hz:int = hit.point.z  hit.transform.position.z;
lineRenderer.SetPosition(0, Camera.main.transform.position+Vector3(0,1,0)); lineRenderer.SetPosition(1, hit.point);
chunkScr = hit.transform.GetComponent(ChunkRender);
chunkScr.chunk[hx,hy,hz] = 0; chunkScr.ChunkRender(chunkScr.chunk); } } }
End of chapter 1!If you guys liked this chapter, please leave a comment asking for more Hell if I get enough comments I may even make a video series. But before you move onto the next chapter, I challenge you to:
 Create a different block size for your chunks
 Make several different types of blocks that spawn
 Make placeable blocks (Hard one!)
 Play around with it and try to understand even more!
Also I do not like to nag but Please sign up to this forum!
It will help you keep in touch with us and all that is going on, you can also send me a personal message and I will help you. But most of all you can follow our voxel game Civil!
Last edited by landon912 on Tue Oct 16, 2012 2:35 pm; edited 1 time in total (Reason for editing : Fixed 2 errors :)) 
