Chunk not building completely

I’m trying to build some voxel terrain however I’m having some issues with some of the faces of the mesh not loading properly. Here’s an image below :

I think it has to do with either how I’m getting the blocks to draw or how I’m drawing them. So that would be either the createVisualMesh function in the Mesh Manager or the isTransparent function in the mesh manager. Below is all the code I think is relevant, if you need more just ask.

Mesh Manager:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MeshManager : MonoBehaviour
{
public static ChunkManager chunkManager;

public static MeshObject CreateVisualMesh(int chunkSize, BlockType[,,] blocks, MeshObject meshOb, Vector3 chunkPos, Chunk chunk)
{
    MeshManager.chunkManager = World.currentWorld.chunkManager;

    Mesh visualMesh = meshOb.Mesh;
    MeshFilter meshFilter = meshOb.MeshFilter;
    MeshCollider meshCollider = meshOb.MeshCollider;

    List<Vector3> verts = new List<Vector3>();
    List<Vector2> uvs = new List<Vector2>();
    List<int> tris = new List<int>();

    int[,,] map = Chunk.GetIntFromBlockType(blocks, chunkSize);

    Vector3 worldPos = new Vector3(chunkPos.x * chunkSize, chunkPos.y * chunkSize, chunkPos.z * chunkSize);

    for (int x = 0; x < chunkSize; x++)
    {
        for (int y = 0; y < chunkSize; y++)
        {
            for (int z = 0; z < chunkSize; z++)
            {
                if (map[x, y, z] == 0) continue;

                int block = map[x, y, z];


                // Left wall
                if (IsTransparent(x - 1, y, z, worldPos, chunk))
                {
                    BuildFace(block, new Vector3(x, y, z), Vector3.up, Vector3.forward, false, verts, uvs, tris);
                }

                // Right wall
                if (IsTransparent(x + 1, y, z, worldPos, chunk))
                {
                    BuildFace(block, new Vector3(x + 1, y, z), Vector3.up, Vector3.forward, true, verts, uvs, tris);
                }

                // Bottom wall
                if (IsTransparent(x, y - 1, z, worldPos, chunk))
                {
                    BuildFace(block, new Vector3(x, y, z), Vector3.forward, Vector3.right, false, verts, uvs, tris);
                }

                // Top wall
                if (IsTransparent(x, y + 1, z, worldPos, chunk))
                {
                    BuildFace(block, new Vector3(x, y + 1, z), Vector3.forward, Vector3.right, true, verts, uvs, tris);
                }

                // Back
                if (IsTransparent(x, y, z - 1, worldPos, chunk))
                {
                    BuildFace(block, new Vector3(x, y, z), Vector3.up, Vector3.right, true, verts, uvs, tris);
                }

                // Front
                if (IsTransparent(x, y, z + 1, worldPos, chunk))
                {
                    BuildFace(block, new Vector3(x, y, z + 1), Vector3.up, Vector3.right, false, verts, uvs, tris);
                }


            }
        }
    }

    visualMesh.vertices = verts.ToArray();
    visualMesh.uv = uvs.ToArray();
    visualMesh.triangles = tris.ToArray();
    visualMesh.RecalculateBounds();
    visualMesh.RecalculateNormals();

    meshFilter.mesh = visualMesh;
    meshCollider.sharedMesh = visualMesh;

    return new MeshObject(visualMesh, meshCollider, meshFilter);
}

public static void BuildFace(int block, Vector3 corner, Vector3 up, Vector3 right, bool reversed, List<Vector3> verts, List<Vector2> uvs, List<int> tris)
{
    int index = verts.Count;

    Vector2 uvCorner = GetTexturePos(block);

    verts.Add(corner);
    verts.Add(corner + up);
    verts.Add(corner + up + right);
    verts.Add(corner + right);

    Vector2 uvWidth = new Vector2(0.22f, 0.22f);

    uvs.Add(uvCorner);
    uvs.Add(new Vector2(uvCorner.x, uvCorner.y + uvWidth.y));
    uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y + uvWidth.y));
    uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y));

    if (reversed)
    {
        tris.Add(index + 0);
        tris.Add(index + 1);
        tris.Add(index + 2);
        tris.Add(index + 2);
        tris.Add(index + 3);
        tris.Add(index + 0);
    }
    else
    {
        tris.Add(index + 1);
        tris.Add(index + 0);
        tris.Add(index + 2);
        tris.Add(index + 3);
        tris.Add(index + 2);
        tris.Add(index + 0);
    }
}

public static int[,,] GetMap(int chunkSize, List<Block> blocks)
{
    int[,,] map = new int[chunkSize, chunkSize, chunkSize];
    for (int x = 0; x < chunkSize; x++)
    {
        for (int y = 0; y < chunkSize; y++)
        {
            for (int z = 0; z < chunkSize; z++)
            {
                try
                {
                    map[x, y, z] = (int)blocks[x + y + z].Id;
                }
                catch
                {

                    Debug.Log(x + y + z);
                }
            }
        }
    }

    return map;
}

public static bool IsTransparent(int x, int y, int z, Vector3 worldPos, Chunk chunk)
{

    BlockType block = GetByte(x, y, z, worldPos, chunk);

    switch (block)
    {
        case BlockType.air:
            return true;
        default:
            return false;
    }
}

public static BlockType GetByte(int x, int y, int z, Vector3 worldPos, Chunk chunk)
{

    if ((x < 0) || (y < 0) || (z < 0) || (y >= chunk.chunkSize) || x >= chunk.chunkSize || z >= chunk.chunkSize)
    {
        Vector3 vec = new Vector3(x + worldPos.x, y + worldPos.y, z + worldPos.z);
        if(chunkManager.GetChunkByWorldPos(vec) == null)
        {
            return Generate.GetTheoreticalByte(vec, chunk.biome);
        }

        return chunkManager.GetBlockByWorldPos(vec);
    }

    return chunk.GetBlockFromPosition(x, y, z);

}

public static Vector2 GetTexturePos(int block)
{
    int b1 = block;
    float lenght = 0.00f;
    float height = 0.75f;



    while (b1 >= 1)
    {
        if (b1 < 17)
        {
            lenght += 0.25f;
            b1 -= 1;
        }
        else
        {
            height -= 0.25f;
            b1 -= 16;
        }
    }
    return new Vector2(lenght + 0.02f, height + 0.02f);

}

private static List<Block> GetBlockListFromMap(int[,,] map, int chunkSize)
{
    List<Block> blockList = new List<Block>();
    for (int x = 0; x < chunkSize; x++)
    {
        for (int y = 0; y < chunkSize; y++)
        {
            for (int z = 0; z < chunkSize; z++)
            {
                blockList.Add(Block.getBlock((BlockType)map[x, y, z]));
            }
        }
    }
    return blockList;
}
}

Chunk :

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;



[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshCollider))]
[RequireComponent(typeof(MeshFilter))]
public class Chunk : MonoBehaviour
{

public BlockType[,,] blockList;
public int chunkSize;
public Vector3 chunkPos;
public Biomes biome;
public Vector3 worldPos;

private Mesh mesh;
private MeshFilter meshFilter;
private MeshCollider meshCollider;
private MeshRenderer meshRenderer;
public bool isVisble = false;
private bool previousVisible = false;
public bool created = false;

public void Start()
{

}

public void Update()
{
    if (isVisble == true && previousVisible == false)
    {
        gameObject.GetComponent<MeshRenderer>().enabled = true;
        meshCollider.enabled = true;
        previousVisible = true;
    }

    if (isVisble == false && previousVisible == true)
    {
        gameObject.GetComponent<MeshRenderer>().enabled = false;
        meshCollider.enabled = false;
        previousVisible = false;
    }

}

public BlockType GetBlockFromPosition(Vector3 blockPos)
{
    return GetBlockFromPosition(Mathf.FloorToInt(blockPos.x), Mathf.FloorToInt(blockPos.y), Mathf.FloorToInt(blockPos.z));
}

public MeshCollider MeshCollider
{
    get
    {
        return meshCollider;
    }

    set
    {
        meshCollider = value;
    }
}

public MeshFilter MeshFilter
{
    get
    {
        return meshFilter;
    }

    set
    {
        meshFilter = value;
    }
}

public Mesh Mesh
{
    get
    {
        return mesh;
    }

    set
    {
        mesh = value;
    }
}

public Vector3 ChunkPos
{
    get
    {
        return chunkPos;
    }

    set
    {
        chunkPos = value;
    }
}

public void SetChunk(Chunk chunk)
{
    this.blockList = chunk.blockList;
    this.chunkSize = chunk.chunkSize;
    this.MeshCollider = chunk.MeshCollider;
    this.MeshFilter = chunk.MeshFilter;
    this.Mesh = chunk.Mesh;
    this.ChunkPos = chunk.ChunkPos;
}

public void CreateChunk(Biomes biome)
{
    this.worldPos = transform.position;

    blockList = new BlockType[chunkSize, chunkSize, chunkSize];

    this.biome = biome;

    this.blockList = Generate.GenerateChunk(chunkSize, chunkPos, 20, biome);

    BuildFullMesh();

    created = true;
}

private void BuildFullMesh()
{
    BlockType[,,] list = blockList;
    MeshObject meshObject = new MeshObject(new Mesh(),
        gameObject.GetComponent<MeshCollider>(), gameObject.GetComponent<MeshFilter>());

    meshObject = MeshManager.CreateVisualMesh(chunkSize, list, meshObject, chunkPos, this);

    mesh = meshObject.Mesh;
    meshFilter = meshObject.MeshFilter;
    meshCollider = meshObject.MeshCollider;

}

public void SetBlock(BlockType block, Vector3 worldPos)
{
    worldPos = World.currentWorld.chunkManager.getBlockFromPos(worldPos);
    SetBlock(block, (int)worldPos.x, (int)worldPos.y, (int)worldPos.z);
}

public void SetBlock(BlockType block, int x, int y, int z)
{
    Debug.Log(x + ":" + y + ":" + z);

    blockList[x, y, z] = block;
    BuildFullMesh();



}

public string BlockListToString()
{
    string list = "";

    for (int x = 0; x < chunkSize; x++)
    {
        for (int y = 0; y < chunkSize; y++)
        {
            for (int z = 0; z < chunkSize; z++)
            {
                list += ", " + x + " : " + y + " : " + z + " = " + this.blockList[x, y, z];
            }
        }
    }

    return list;
}

public BlockType GetBlockFromPosition(int x, int y, int z)
{
    //Debug.Log(x + ":" + y + ":" + z);
    return blockList[x, y, z];

}

public static int[,,] GetIntFromBlockType(BlockType[,,] blocks, int chunkSize)
{
    int[,,] map = new int[chunkSize, chunkSize, chunkSize];
    for (int x = 0; x < chunkSize; x++)
    {
        for (int y = 0; y < chunkSize; y++)
        {
            for (int z = 0; z < chunkSize; z++)
            {
                map[x, y, z] = (int)blocks[x, y, z];
            }
        }
    }

    return map;
}

}

ChunkManager:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChunkManager {


private Dictionary<Vector3, Chunk> chunkList;

public ChunkManager()
{
    chunkList = new Dictionary<Vector3, Chunk>();
}

public BlockType GetBlockByWorldPos(Vector3 p)
{
    Chunk chunk = GetChunkByWorldPos(p);

    Vector3 blockPos = getBlockFromPos(p);

    return chunk.GetBlockFromPosition(blockPos);
}

public BlockType GetMeshBuildBlock(Vector3 pos)
{
    if(GetChunkByWorldPos(pos) == null)
    {
        return Generate.GetTheoreticalByte(pos, Biomes.plains);
    }
    else
    {
        Vector3 pos1 = getBlockFromPos(pos);

        return GetChunkByWorldPos(pos).GetBlockFromPosition(pos1);
    }
}

public void SetBlockByWorldPos(Vector3 p, BlockType block)
{
    //Debug.Log(p.x + ":" + p.y + ":" + p.z + ":");
    Chunk chunk = GetChunkByWorldPos(p);

    Vector3 blockPos = getBlockFromPos(p);

    chunk.SetBlock(block, blockPos);
}

public Vector3 getBlockFromPos(Vector3 pos)
{
    Vector3 vector3 = new Vector3();
    vector3.x = Mathf.Abs(pos.x) % World.staticChunkSize;
    vector3.y = Mathf.Abs(pos.y) % World.staticChunkSize;
    vector3.z = Mathf.Abs(pos.z) % World.staticChunkSize;

    return vector3;
}

public void AddChunkToList(Chunk chunk)
{
    chunkList.Add(chunk.chunkPos, chunk);
}

public Chunk GetChunkByPos(Vector3 pos)
{
    if (chunkList.ContainsKey(pos))
    {
        return chunkList[pos];
    }
    return null;
}

public Chunk GetChunkByWorldPos(Vector3 pos)
{
    Vector3 chunkPos = new Vector3(Mathf.Floor((pos.x / World.staticChunkSize)),
        Mathf.Floor((pos.y / World.staticChunkSize)),
        Mathf.Floor((pos.z / World.staticChunkSize)));
    return GetChunkByPos(chunkPos);
}}

I don’t know where the problem is, but in your IsTransparent method you should define your switch cases explicitly and throw an exception by default, like this:

switch (block)
{
    case BlockType.air:
        return true;
    case BlockType.something:
    case BlockType.somethingElse:
        return false;
    default:
        throw new ArgumentException();
}

It is much easier to catch potential bugs.

1 Like

Thanks for the advice. It makes a lot of sense.

Try zooming in and around the mesh and see if the triangles are being drawn backwards in those cases (so, facing the wrong direction). I can’t see where the problem is, but my gut is saying that it’s either in the DrawFace function and involves the triangles not being draw in the correct order in some cases, or in the GetTexturePos function. I would expect both of these potential issues to cause more problems than 1 or 2 missing faces though…

I did that, the faces aren’t being drawn backwards in those places but there are faces being drawn where they shouldn’t be. I checked but they don’t line up with the missing faces, but these faces seems to be drawn on any chunk that has missing faces (as far as I can tell). Does this give you any clues?

On line 194 in the Mesh Manager, should it be if(b1 < 16) instead of 17?

I went over it line by line and that’s the only thing that actually stands out to me, sorry. You may have to do some pretty extensive debugging, maybe rendering ONLY the blocks that are having problems and see if you can narrow down exactly where the problem is occurring by logging all of the data of rendering it, at each step.

Due to some texture weirdness in how I made the sprite sheet it’s actually correct, but I’m making a new one that will change this again but that’s not the issue. In the mesh manager, GetByte() I changed it so that only the Generate.GetTheoreticalByte() (so removing the chunkManager.GetBlockByWorldPos() )and everything renders fine. The issue with this though is that when you remove the a block and rebuild the mesh it doesn’t rendered the block faces around the block as it still thinks it is the original block (since it’s generating the block again via the Perlin noise). So the issue should be in the GetBlockByWorldPos() method.

Right now I am too busy to look closely at your code, but isn’t there (again) problem with casting to int in your SetBlock method?

public void SetBlock(BlockType block, Vector3 worldPos)
{
    worldPos = World.currentWorld.chunkManager.getBlockFromPos(worldPos);
    SetBlock(block, (int)worldPos.x, (int)worldPos.y, (int)worldPos.z);
}

Yeah I haven’t got round to fixing it because of the generation bug (which doesn’t have anything to do with the set block function) as well as life getting in the way. There’s not setting the blocks in the generation of the chunks, I create the block array into that generation so there’s no need.

Anyone have any ideas?

Not likely to get much more- you need to debug it more carefully. Break down the mesh into the smallest possible sample size to reproduce the problem. Debug log the outputs one cube at a time to find a cube with the issue, then just try with that cube by itself and debug log one side at a time, then one vertex at a time, etc, so you can see exactly where the problem is occurring. Once you know where it’s occurring, it’ll be much easier to fix it. There’s only so much that people can do to help you without being able to reproduce the issue ourselves- nothing in the code “looks” wrong.

Alright, thanks for the advice.

So I found where everything was going wrong. Here

public Vector3 getBlockFromPos(Vector3 pos)
{
    Vector3 vector3 = new Vector3();
    vector3.x = Mathf.Abs(pos.x) % World.staticChunkSize;
    vector3.y = Mathf.Abs(pos.y) % World.staticChunkSize;
    vector3.z = Mathf.Abs(pos.z) % World.staticChunkSize;
    return vector3;
}

This is in the ChunkManager. The issue with this code is when the numbers are minus it meshes up. I think the reason why I wasn’t seeing such a dramatic effect is because of the smooth noise function I’m using. So if I when I changed it to this it fixed the issue I was having.

 public Vector3 getBlockFromPos(Vector3 pos)
    {
        Vector3 vector3 = new Vector3();
        vector3.x = pos.x % World.staticChunkSize;
        vector3.y = pos.y % World.staticChunkSize;
        vector3.z = pos.z % World.staticChunkSize;

        if(vector3.x < 0 || vector3.x >= World.staticChunkSize)
        {
            if(vector3.x < 0)
            {
                vector3.x += World.staticChunkSize;
            } else
            {
                vector3.x -= World.staticChunkSize;
            }
        }

        if (vector3.y < 0 || vector3.y >= World.staticChunkSize)
        {
            if (vector3.y < 0)
            {
                vector3.y += World.staticChunkSize;
            }
            else
            {
                vector3.y -= World.staticChunkSize;
            }
        }

        if (vector3.z < 0 || vector3.z >= World.staticChunkSize)
        {
            if (vector3.z  < 0)
            {
                vector3.z += World.staticChunkSize;
            }
            else
            {
                vector3.z -= World.staticChunkSize;
            }
        }

        return vector3;
    }

Hopefully this might help someone in the future.

1 Like

If it should be simple modulo, then you can probably refactor it like this:

public Vector3 getBlockFromPos(Vector3 pos)
{
    return new Vector3
    (
        pos.x - World.staticChunkSize * Mathf.Floor(pos.x / World.staticChunkSize),
        pos.y - World.staticChunkSize * Mathf.Floor(pos.y / World.staticChunkSize),
        pos.z - World.staticChunkSize * Mathf.Floor(pos.z / World.staticChunkSize)
    );
}