After my success with marching cubes, I'm next planning to create a cubic voxel terrain system like Minecraft, given the design is better for gameplay simplicity and performance alike. I've played with SurfaceTool and know how to spawn a plane, albeit I wouldn't mind a full answer including how the faces are best drawn in this circumstances. My actual question is about how to spawn those planes, notably how I can optimize them in such a way so that continuous faces forming a square or rectangle are represented as one face. Obviously I want this optimization for rendering as well as physics performance: Apart from not generating internal faces between non-transparent voxels which is a given, even more triangles can be avoided by packing identical faces into a single quad.
For a Blender visualization of what I hope to achieve, let's say we have the following chunk. Every voxel is solid and of the same type so textures would match and they can be merged. Without any surface packing this is what we have:

Now we run the list of faces through a surface packer before generating the geometry, which returns a smaller list of larger surfaces in the same format. When feeding that to the generator instead we would get this:

That's so much fewer surfaces to draw! Of course the question is how do I best detect which faces form a square or rectangle then generate a single face encompassing them? The logic behind scan is a bit hard to warp my mind around. It needs to be efficient else the packing process is too expensive and makes the generator slow. Further more there are multiple patterns in which you can detect and decide which surfaces to merge, I'd want the one that results in the fewest surfaces possible. Obviously the UV's need to be adjusted to keep the texture tiled to the same universal scale but that should be an easy one.
Until voxel types and chunk storage are implemented, the system will start by using an OpenSimplexNoise where a value greater than zero is full and one equal or lesser than zero is empty, so for simplicity's sake we can work with that. The loop that scans the chunk data and generates faces from it will thus be something like this assuming a voxel size of 1:
var faces = []
for x in range(chunk_mins.x, chunk_maxs.x + 1):
for y in range(chunk_mins.y, chunk_maxs.y + 1):
for z in range(chunk_mins.z, chunk_maxs.z + 1):
var pos = Vector3(x, y, z)
var pos_+x = Vector3(x + 1, y, z)
var pos_-x = Vector3(x - 1, y, z)
var pos_+y = Vector3(x, y + 1, z)
var pos_-x = Vector3(x, y - 1, z)
var pos_+z = Vector3(x, y, z + 1)
var pos_-z = Vector3(x, y, z - 1)
if noise(pos) && !noise(pos_+x):
# There's a solidity difference in the +X direction, generate a face
faces.append({
corner_start = Vector3(pos.x + 0.5, pos.y - 0.5, pos.z - 0.5),
corner_end = Vector3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5),
invert = false
})
# Repeat the above if check for all other neighbor directions
generate_faces(faces)
It's worth noting that unlike conventional MC terrain the generator will support variable voxel sizes and LOD levels. This includes values less than 1 which might be tricky since the range function doesn't like decimals, I remember trying things like "for i in range(-0.5, 0.5, 0.25)" and it would error out saying it only wants integers. But it should always be multiples or divisions of two, so chunk resolutions may range in the form [0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16]. Let me know what you believe is the best way and how you suggest going about this! Thank you.