// Create a single chunk's matrix of nodes // arguements are the chunks index, not their true positions void CreateChunkMatrix(int x, int y, int z) { // For heirarchy cleaning purposes I create multiple game objects to be nested within eachother ChunkMatrix currChunkMatrix = new ChunkMatrix(xChunkCubeLength, yChunkCubeLength, zChunkCubeLength); GameObject chunkObj = Instantiate(chunk_parent, Vector3.zero, Quaternion.identity); chunkObj.transform.parent = mapHolder.transform; chunkObj.tag = "Chunk"; currChunkMatrix.chunkObj = chunkObj; Mesh mesh = new Mesh(); mesh.SetVertices(currChunkMatrix.vertices); mesh.SetTriangles(currChunkMatrix.indices, 0); currChunkMatrix.chunkMesh = new GameObject("Mesh"); currChunkMatrix.chunkMesh.transform.parent = currChunkMatrix.chunkObj.transform; currChunkMatrix.chunkMesh.AddComponent <MeshCollider>(); currChunkMatrix.chunkMesh.AddComponent <MeshFilter>(); currChunkMatrix.chunkMesh.AddComponent <MeshRenderer>(); currChunkMatrix.chunkMesh.GetComponent <Renderer>().material = mesh_material; currChunkMatrix.chunkMesh.GetComponent <MeshFilter>().mesh = mesh; currChunkMatrix.chunkMesh.GetComponent <MeshCollider>().sharedMesh = mesh; currChunkMatrix.chunkMesh.tag = "Mesh"; currChunkMatrix.chunkMesh.transform.localPosition = Vector3.zero; // this is like the local positions of the chunk with respect to other chunks currChunkMatrix.chunksIndex = new int [] { x, y, z }; // this is the true position of the start of the chunk in unity units... or whatever currChunkMatrix.position = new Vector3(x * xChunkLength / (float)cubeResolution, y * yChunkLength / (float)cubeResolution, z * zChunkLength / (float)cubeResolution); AddNodesToChunk(currChunkMatrix); AddNoiseToChunk(currChunkMatrix); }
// Function to change values in the noise map // x,y,z are the nodes position inside the chunk void SetNoiseValues(ChunkMatrix currChunk, int x, int y, int z, float isovalue, int width) { float fWidth = width; // / (float)cubeResolution; // get the indices in the noise map for the supplied position int[] hitIndices = emptyHitIndices; hitIndices[0] = currChunk.nodes[x, y, z].noiseIndices[0]; hitIndices[1] = currChunk.nodes[x, y, z].noiseIndices[1]; hitIndices[2] = currChunk.nodes[x, y, z].noiseIndices[2]; int[] chunkIndices = emptyChunkIndices; chunkIndices[0] = currChunk.chunksIndex[0]; chunkIndices[1] = currChunk.chunksIndex[1]; chunkIndices[2] = currChunk.chunksIndex[2]; // if its in the middle of the chunks if (chunkIndices[0] > 0 && chunkIndices[0] < xChunks - 1 && chunkIndices[1] > 0 && chunkIndices[1] < yChunks - 1 && chunkIndices[2] > 0 && chunkIndices[2] < zChunks - 1) { for (int y_ = hitIndices[1] - width; y_ < hitIndices[1] + width; y_++) { for (int z_ = hitIndices[2] - width; z_ < hitIndices[2] + width; z_++) { for (int x_ = hitIndices[0] - width; x_ < hitIndices[0] + width; x_++) { // Create vectors to check the distances between the nodes within the range of the hit point distanceP1.x = x_; distanceP1.y = y_; distanceP1.z = z_; distanceP2.x = hitIndices[0]; distanceP2.y = hitIndices[1]; distanceP2.z = hitIndices[2]; float checkDst = Vector3.Distance(distanceP1, distanceP2); if (checkDst <= fWidth) { mapNoise[x_, y_, z_] += isovalue; if (mapNoise[x_, y_, z_] > 1) { mapNoise[x_, y_, z_] = 1; } else if (mapNoise[x_, y_, z_] < 0) { mapNoise[x_, y_, z_] = 0; } } } } } } else { Debug.Log(chunkIndices[0] + ", " + chunkIndices[1] + ", " + chunkIndices[2]); // tells the console that it clicked on an edge rather than somewhere in the middle } }
// Function to reset the chunk's mesh data ChunkMatrix ResetChunkMeshData(ChunkMatrix hitChunk) { hitChunk.vertices.Clear(); hitChunk.indices.Clear(); hitChunk.vertexCount = 0; // reset the noise in the chunk AddNoiseToChunk(hitChunk); return(hitChunk); }
// This function sets up the cube-by-cube marching within the polygonize function for a specific chunk void MarchThroughChunk(ChunkMatrix currChunk) { for (int y = 1; y < currChunk.nodes.GetLength(1) - 1; y++) { for (int x = 1; x < currChunk.nodes.GetLength(0) - 1; x++) { for (int z = 1; z < currChunk.nodes.GetLength(2) - 1; z++) { currChunk = PolygonizeCube(currChunk, x, y, z); } } } }
// Add all of the necessary nodes to the chunk matrix void AddNodesToChunk(ChunkMatrix currChunk) { for (int y = 0; y < yChunkCubeLength; y++) { for (int z = 0; z < zChunkCubeLength; z++) { for (int x = 0; x < xChunkCubeLength; x++) { currChunk.nodes[x, y, z] = new Node(Vector3.zero, 0f); } } } // place the current chunk into the chunk array chunks[currChunk.chunksIndex[0], currChunk.chunksIndex[1], currChunk.chunksIndex[2]] = currChunk; }
// Check which vertices around the cube are active (above surfaceLevel public static int CheckVertices(ChunkMatrix currChunk, ChunkMatrix[,,] chunks, float surfaceLevel, int x, int y, int z) { // Initialize the cube index int cubeIndex = 0; // get nodes of the current chunk Node[,,] nodes = currChunk.nodes; // check which vertices around the cube are active // the order assigned in this mannor is essential for how the LUT(look up tables) are set up // the order corresponds to the vertices in Paul Burke's graphic on his 1994 site about Polygonizing a scalar field if (nodes[x, y, z + 1].isovalue <= surfaceLevel) // vertex 0 { cubeIndex |= 1; } if (nodes[x + 1, y, z + 1].isovalue <= surfaceLevel) // vertex 1 { cubeIndex |= 2; } if (nodes[x + 1, y, z].isovalue <= surfaceLevel) // vertex 2 { cubeIndex |= 4; } if (nodes[x, y, z].isovalue <= surfaceLevel) // vertex 3 { cubeIndex |= 8; } if (nodes[x, y + 1, z + 1].isovalue <= surfaceLevel) // vertex 4 { cubeIndex |= 16; } if (nodes[x + 1, y + 1, z + 1].isovalue <= surfaceLevel) // vertex 5 { cubeIndex |= 32; } if (nodes[x + 1, y + 1, z].isovalue <= surfaceLevel) // vertex 6 { cubeIndex |= 64; } if (nodes[x, y + 1, z].isovalue <= surfaceLevel) // vertex 7 { cubeIndex |= 128; } return(cubeIndex); }
// Function to draw the mesh of a given chunk // x y z are the indicies in the chunk matrix for which chunk youre referencing not the chunks position void DrawSingleMesh(ChunkMatrix currChunk) { MeshFilter filter = currChunk.chunkMesh.GetComponent <MeshFilter>(); Mesh mesh = filter.mesh; filter.mesh = null; mesh.Clear(); mesh.SetVertices(currChunk.vertices); mesh.SetTriangles(currChunk.indices, 0); mesh.RecalculateNormals(); filter.mesh = mesh; MeshCollider collider = currChunk.chunkMesh.GetComponent <MeshCollider>(); collider.sharedMesh = null; collider.sharedMesh = mesh; }
// I want to change this so that it just works on the current voxel rather than have the material fly at the user // Update is called once per frame void Update() { CameraMovement(); if (Input.GetMouseButton(0) || Input.GetMouseButton(1)) { // dir variable is used to dictate whether the cubes are being deleted or added to int dir = 0; if (Input.GetMouseButton(0)) { dir = 1; } else if (Input.GetMouseButton(1)) { dir = -1; } RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out hit, 20f) && hit.transform.tag == "Mesh") { Vector3 hitPos = hit.point; int[] currentChunkIndices = GetCurrentChunkFromPos(hitPos); int[] currentVoxelIndices = GetCurrentCubeInChunkFromPos(hitPos); // get the chunk that the ray hit ChunkMatrix hitChunk = chunks[currentChunkIndices[0], currentChunkIndices[1], currentChunkIndices[2]]; // change the noise values around the affected point SetNoiseValues(hitChunk, currentVoxelIndices[0], currentVoxelIndices[1], currentVoxelIndices[2], dir * editValue, radius); // update the chunks around the affected mesh UpdateSurroundingChunks(currentChunkIndices[0], currentChunkIndices[1], currentChunkIndices[2]); } } }
// Function to fill the chunk matrix with the noise values // x,y,z are chunk's indices void AddNoiseToChunk(ChunkMatrix currChunk) { int x = currChunk.chunksIndex[0]; int y = currChunk.chunksIndex[1]; int z = currChunk.chunksIndex[2]; float currNoise = 0; // iterate through the noiseMap and add the noise to the current node in the chunk matrix for (int _y = 0, k = y * (yChunkCubeLength - 2); k < y * (yChunkCubeLength - 2) + yChunkCubeLength; k++, _y++) { for (int _z = 0, j = z * (zChunkCubeLength - 2); j < z * (zChunkCubeLength - 2) + zChunkCubeLength; j++, _z++) { for (int _x = 0, i = x * (xChunkCubeLength - 2); i < x * (xChunkCubeLength - 2) + xChunkCubeLength; i++, _x++) { // get the noise from the large noise map currNoise = mapNoise[i, k, j]; // define the node's position in unity space' //Debug.Log(currChunk.nodes[_x, _y, _z].position.x); currChunk.nodes[_x, _y, _z].position.x = _x / (float)cubeResolution + x * xChunkLength; currChunk.nodes[_x, _y, _z].position.y = _y / (float)cubeResolution + y * yChunkLength; currChunk.nodes[_x, _y, _z].position.z = _z / (float)cubeResolution + z * zChunkLength; // define the node's isovalue currChunk.nodes[_x, _y, _z].isovalue = currNoise; // define the node's location on the noise map currChunk.nodes[_x, _y, _z].noiseIndices[0] = i; currChunk.nodes[_x, _y, _z].noiseIndices[1] = k; currChunk.nodes[_x, _y, _z].noiseIndices[2] = j; // add the working matrix to the array of chunks chunks[x, y, z] = currChunk; } } } }
// Function to set a specific chunk's specific node's isovalue // also set the chunks around it ChunkMatrix SetIsovalues(ChunkMatrix currChunk, int x, int y, int z, float isovalue, int width) { float fWidth = width / (float)cubeResolution; for (int k = 0; k < yChunkCubeLength; k++) { for (int j = 0; j < zChunkCubeLength; j++) { for (int i = 0; i < xChunkCubeLength; i++) { float checkDst = Vector3.Distance(currChunk.nodes[i, k, j].position, currChunk.nodes[x, y, z].position); // any nodes within the width of the "brush" if (checkDst <= fWidth) { currChunk.nodes[i, k, j].isovalue += isovalue; } } } } return(currChunk); }
void DestroyChunk(ChunkMatrix currChunk) { // destroy the mesh object from the current chunk Destroy(currChunk.chunkMesh); }
// Function to check each corner of a given cube - this is the "March" of Marching Cubes // The x,y,z arguments are the positions of nodes in a chunk's node matrix // The currChunk argument is the nodes within the current chunk's matrix data ChunkMatrix PolygonizeCube(ChunkMatrix currChunk, int x, int y, int z) { Node[,,] nodes = currChunk.nodes; // get the type of cube dependant on the active vertices within it int cubeIndex = MarchingCubes.CheckVertices(currChunk, chunks, surfaceLevel, x, y, z); // if there were no active nodes in this cube, break the function if (cubeIndex == 0) { return(currChunk); } // use the edge table to obtain which edges are active for this cube configuration int edges = MarchingCubes.edgeTable[cubeIndex]; // array holding the positions of each edge vertex Vector3[] edgeVertices = emptyVertices; // value of which vertex is being worked on at the moment for the mesh creation int vert; // Find the vertices for the triangles in the cube - TODO: do the interpolation here ***************************************** for (int i = 0; i < edgeVertices.Length; i++) // edge vertices length is the numver of edges in the cube { if ((edges & (1 << i)) != 0) // checks the boolean values from the edges table to see which edges are active { // start by assuming a hard edge - turn softEdge to false to create a more flat terrain float edgeOffset = 1 / (float)cubeResolution / 2f; // turning on softEdge will use the linear interpolation to smooth out the edges if (softEdge) { float[] cube = MarchingCubes.GetIsovaluesOfCube(nodes, x, y, z); edgeOffset = MarchingCubes.CalculateEdgeOffset(cube[MarchingCubes.edgeConnection[i, 0]], cube[MarchingCubes.edgeConnection[i, 1]], surfaceLevel) / (float)cubeResolution; } // The edgeVertices array holds the position for where the vertex will be drawn along the edge // To figure this out, we need the position of the current node added to the // VertexOffset defined by the LUT that shows where the next edge is in comparison to this current one // The vertex offset needs to be divided by the cube resolution because the distance between edges will change if there are more cubes in the same amount of space // The edgeOffset is the linear interpolated value we found and it is basically a percentage of how far between the current and next node that the vertex should be placed // The edgeOffset needs to be multiplied by the edgeDirection value in order to know which direction that the offset is in edgeVertices[i].x = nodes[x, y, z].position.x + MarchingCubes.vertexOffset[MarchingCubes.edgeConnection[i, 0], 0] / (float)cubeResolution + edgeOffset * MarchingCubes.edgeDirection[i, 0]; edgeVertices[i].y = nodes[x, y, z].position.y + MarchingCubes.vertexOffset[MarchingCubes.edgeConnection[i, 0], 1] / (float)cubeResolution + edgeOffset * MarchingCubes.edgeDirection[i, 1]; edgeVertices[i].z = nodes[x, y, z].position.z + MarchingCubes.vertexOffset[MarchingCubes.edgeConnection[i, 0], 2] / (float)cubeResolution + edgeOffset * MarchingCubes.edgeDirection[i, 2]; } } // Create the triangles from these verts - there will be at most 5 triangles to be made per cube for (int i = 0; i < 5; i++) { if (MarchingCubes.triTable[cubeIndex, 3 * i] < 0) // no triangles to be made { break; } // get the current number of verts in the mesh int vertexCount = currChunk.vertices.Count; for (int j = 0; j < 3; j++) // iterate through the triangle verts { vert = MarchingCubes.triTable[cubeIndex, 3 * i + j]; currChunk.indices.Add(vertexCount + windingOrder[j]); currChunk.vertices.Add(edgeVertices[vert]); } } return(currChunk); }
// Use this for initialization void Start() { Global.Instance.InitClient(); Global.Instance.CurrentScene = SceneManager.GetActiveScene().name; Global.Instance.MoveMessages.Clear(); entitiesNode = GameObject.FindGameObjectWithTag("Entities"); entityMatrix = new ChunkMatrix<MapEntity>(chunksize); mapMatrix = new ChunkMatrix<GameObject>(chunksize); mapObjMatrix = new ChunkMatrix<GameObject>(chunksize); groundMatrix = new ChunkMatrix<Int32>(chunksize); mapOverlayMatrix = new ChunkMatrix<List<GameObject>>(chunksize); populationMatrix = new ChunkMatrix<List<PopulationEntity>>(chunksize); mapEntities = new Dictionary<Int32, MapEntity>(); }