# [SOLVED] Greedy Meshing doesn't render certain sides

Hey. I was working on a minecraft clone and I needed a greedy meshing algorithm. So I copied Roleary’s meshing algorithm that they posted.

``````void greedy() {

int i, j, k, l, w, h, u, v, n, side = 0;

final int[] x = new int []{0,0,0};
final int[] q = new int []{0,0,0};
final int[] du = new int[]{0,0,0};
final int[] dv = new int[]{0,0,0};

final Voxel[] mask = new Voxel[Constants.CHUNK_WIDTH * Constants.CHUNK_HEIGHT];

Voxel voxelFace, voxelFace1;

for (boolean backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b) {

for(int d = 0; d < 3; d++) {

u = (d + 1) % 3;
v = (d + 2) % 3;

x[0] = 0;
x[1] = 0;
x[2] = 0;

q[0] = 0;
q[1] = 0;
q[2] = 0;
q[d] = 1;

if (d == 0)      { side = backFace ? Constants.WEST   : Constants.EAST;  }
else if (d == 1) { side = backFace ? Constants.BOTTOM : Constants.TOP;   }
else if (d == 2) { side = backFace ? Constants.SOUTH  : Constants.NORTH; }

for(x[d] = -1; x[d] < Constants.CHUNK_WIDTH;) {

n = 0;

for(x[v] = 0; x[v] < Constants.CHUNK_HEIGHT; x[v]++) {

for(x[u] = 0; x[u] < Constants.CHUNK_WIDTH; x[u]++) {

if(x[0] + q[0] <= 63 && x[1] + q[1] <= 255 && x[2] + q[2] <= 63) {
voxelFace  = (x[d] >= 0 )             ? getVoxel(x[0], x[1], x[2], side)                      : null;
voxelFace1 = (x[d] < Constants.CHUNK_WIDTH - 1) ? getVoxel(x[0] + q[0], x[1] + q[1], x[2] + q[2], side) : null;

mask[n++] = ((voxelFace != null && voxelFace1 != null && voxelFace.equals(voxelFace1)))
? null
: backFace ? voxelFace1 : voxelFace;

}
}
}

x[d]++;

n = 0;

for(j = 0; j < Constants.CHUNK_HEIGHT; j++) {

for(i = 0; i < Constants.CHUNK_WIDTH;) {

for(w = 1; i + w < Constants.CHUNK_WIDTH && mask[n + w] != null && mask[n + w].equals(mask[n]); w++) {}

boolean done = false;

for(h = 1; j + h < Constants.CHUNK_HEIGHT; h++) {

for(k = 0; k < w; k++) {

if(mask[n + k + h * Constants.CHUNK_WIDTH] == null || !mask[n + k + h * Constants.CHUNK_WIDTH].equals(mask[n])) { done = true; break; }
}

if(done) { break; }
}

x[u] = i;
x[v] = j;

du[0] = 0;
du[1] = 0;
du[2] = 0;
du[u] = w;

dv[0] = 0;
dv[1] = 0;
dv[2] = 0;
dv[v] = h;

new Vector3f(x[0] + du[0],         x[1] + du[1],           x[2] + du[2]),
new Vector3f(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1],   x[2] + du[2] + dv[2]),
new Vector3f(x[0] + dv[0],         x[1] + dv[1],           x[2] + dv[2]),
w,
h,
backFace);
}

for(l = 0; l < h; ++l) {

for(k = 0; k < w; ++k) { mask[n + k + l * Constants.CHUNK_WIDTH] = null; }
}

i += w;
n += w;

} else {

i++;
n++;
}
}
}
}
}
}
}

Voxel getVoxel(final int x, final int y, final int z, final int side) {

Voxel voxelFace = voxels[x][y][z];

voxelFace.side = side;

return voxelFace;
}

final Vector3f topLeft,
final Vector3f topRight,
final Vector3f bottomRight,
final int width,
final int height,
final Voxel voxel,
final boolean backFace) {

final Vector3f [] vertices = new Vector3f[4];

vertices[2] = topLeft.multLocal(Constants.VOXEL_SIZE);
vertices[3] = topRight.multLocal(Constants.VOXEL_SIZE);
vertices[0] = bottomLeft.multLocal(Constants.VOXEL_SIZE);
vertices[1] = bottomRight.multLocal(Constants.VOXEL_SIZE);

final int [] indexes = backFace ? new int[] { 2,0,1, 1,3,2 } : new int[]{ 2,3,1, 1,0,2 };

final float[] colorArray = new float[4*4];

for (int i = 0; i < colorArray.length; i+=4) {

/*
* Here I set different colors for quads depending on the "type" attribute, just
* so that the different groups of voxels can be clearly seen.
*
*/
if (voxel.type == BlockType.GRASS_BLOCK) {

colorArray[i]   = 0.1f;
colorArray[i+1] = 0.3f;
colorArray[i+2] = 0.0f;
colorArray[i+3] = 1.0f;

} else {

colorArray[i]   = 0.0f;
colorArray[i+1] = 0.0f;
colorArray[i+2] = 0.0f;
colorArray[i+3] = 0.0f;
}
}

Mesh mesh = new Mesh();

mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
mesh.setBuffer(Type.Color,    4, colorArray);
mesh.setBuffer(Type.Index,    3, BufferUtils.createIntBuffer(indexes));
mesh.updateBound();

Geometry geo = new Geometry("ColoredMesh", mesh);

if(voxel.type == BlockType.AIR)geo.setCullHint(CullHint.Always);

Material mat = new Material(main.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setBoolean("VertexColor", true);

/*
* To see the actual rendered quads rather than the wireframe, just comment outthis line.
*/

geo.setMaterial(mat);

attachChild(geo);

this.setCullHint(CullHint.Never);
}
``````

then I created a voxel class, chunk class and worldManager class:

Voxel:

``````package mygame;

public class Voxel {

public boolean transparent;
public int type;
public int side;

public boolean equals(final Voxel voxel) {
return voxel.transparent == this.transparent && voxel.type == this.type;
}

}

``````

Chunk:

``````package mygame;

import com.jme3.material.Material;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

/**
*
* @author user
*/
public class Chunk extends Node {

WorldManager worldManager;
Main main;
NoiseGenerator noiseGen;
Vector3f loc;
Voxel[][][] voxels = new Voxel[Constants.CHUNK_WIDTH][Constants.CHUNK_HEIGHT][Constants.CHUNK_WIDTH];

public Chunk(Vector3f loc, WorldManager worldManager, Main main) {
this.worldManager = worldManager;
this.main = main;
this.noiseGen = worldManager.noiseGen;
this.loc = loc;

generate();
greedy();
this.setLocalTranslation(loc);
}

public void generate() {

for(int xx = 0; xx < Constants.CHUNK_WIDTH; xx++) {
for(int yy = 0; yy < Constants.CHUNK_HEIGHT; yy++) {
for(int zz = 0; zz < Constants.CHUNK_WIDTH; zz++) {
Voxel voxel = new Voxel();

if(yy <= (int) ((noiseGen.noise(xx + (int) loc.x, zz + (int) loc.z) + 1) * 7)) {
voxel.type = BlockType.GRASS_BLOCK;
} else voxel.type=BlockType.AIR;

voxels[xx][yy][zz] = voxel;
}
}
}

}

void greedy() {

int i, j, k, l, w, h, u, v, n, side = 0;

final int[] x = new int []{0,0,0};
final int[] q = new int []{0,0,0};
final int[] du = new int[]{0,0,0};
final int[] dv = new int[]{0,0,0};

final Voxel[] mask = new Voxel[Constants.CHUNK_WIDTH * Constants.CHUNK_HEIGHT];

Voxel voxelFace, voxelFace1;

for (boolean backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b) {

for(int d = 0; d < 3; d++) {

u = (d + 1) % 3;
v = (d + 2) % 3;

x[0] = 0;
x[1] = 0;
x[2] = 0;

q[0] = 0;
q[1] = 0;
q[2] = 0;
q[d] = 1;

if (d == 0)      { side = backFace ? Constants.WEST   : Constants.EAST;  }
else if (d == 1) { side = backFace ? Constants.BOTTOM : Constants.TOP;   }
else if (d == 2) { side = backFace ? Constants.SOUTH  : Constants.NORTH; }

for(x[d] = -1; x[d] < Constants.CHUNK_WIDTH;) {

n = 0;

for(x[v] = 0; x[v] < Constants.CHUNK_HEIGHT; x[v]++) {

for(x[u] = 0; x[u] < Constants.CHUNK_WIDTH; x[u]++) {

if(x[0] + q[0] <= 63 && x[1] + q[1] <= 255 && x[2] + q[2] <= 63) {
voxelFace  = (x[d] >= 0 )             ? getVoxel(x[0], x[1], x[2], side)                      : null;
voxelFace1 = (x[d] < Constants.CHUNK_WIDTH - 1) ? getVoxel(x[0] + q[0], x[1] + q[1], x[2] + q[2], side) : null;

mask[n++] = ((voxelFace != null && voxelFace1 != null && voxelFace.equals(voxelFace1)))
? null
: backFace ? voxelFace1 : voxelFace;

}
}
}

x[d]++;

n = 0;

for(j = 0; j < Constants.CHUNK_HEIGHT; j++) {

for(i = 0; i < Constants.CHUNK_WIDTH;) {

for(w = 1; i + w < Constants.CHUNK_WIDTH && mask[n + w] != null && mask[n + w].equals(mask[n]); w++) {}

boolean done = false;

for(h = 1; j + h < Constants.CHUNK_HEIGHT; h++) {

for(k = 0; k < w; k++) {

if(mask[n + k + h * Constants.CHUNK_WIDTH] == null || !mask[n + k + h * Constants.CHUNK_WIDTH].equals(mask[n])) { done = true; break; }
}

if(done) { break; }
}

x[u] = i;
x[v] = j;

du[0] = 0;
du[1] = 0;
du[2] = 0;
du[u] = w;

dv[0] = 0;
dv[1] = 0;
dv[2] = 0;
dv[v] = h;

new Vector3f(x[0] + du[0],         x[1] + du[1],           x[2] + du[2]),
new Vector3f(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1],   x[2] + du[2] + dv[2]),
new Vector3f(x[0] + dv[0],         x[1] + dv[1],           x[2] + dv[2]),
w,
h,
backFace);
}

for(l = 0; l < h; ++l) {

for(k = 0; k < w; ++k) { mask[n + k + l * Constants.CHUNK_WIDTH] = null; }
}

i += w;
n += w;

} else {

i++;
n++;
}
}
}
}
}
}
}

Voxel getVoxel(final int x, final int y, final int z, final int side) {

Voxel voxelFace = voxels[x][y][z];

voxelFace.side = side;

return voxelFace;
}

final Vector3f topLeft,
final Vector3f topRight,
final Vector3f bottomRight,
final int width,
final int height,
final Voxel voxel,
final boolean backFace) {

final Vector3f [] vertices = new Vector3f[4];

vertices[2] = topLeft.multLocal(Constants.VOXEL_SIZE);
vertices[3] = topRight.multLocal(Constants.VOXEL_SIZE);
vertices[0] = bottomLeft.multLocal(Constants.VOXEL_SIZE);
vertices[1] = bottomRight.multLocal(Constants.VOXEL_SIZE);

final int [] indexes = backFace ? new int[] { 2,0,1, 1,3,2 } : new int[]{ 2,3,1, 1,0,2 };

final float[] colorArray = new float[4*4];

for (int i = 0; i < colorArray.length; i+=4) {

/*
* Here I set different colors for quads depending on the "type" attribute, just
* so that the different groups of voxels can be clearly seen.
*
*/
if (voxel.type == BlockType.GRASS_BLOCK) {

colorArray[i]   = 0.1f;
colorArray[i+1] = 0.3f;
colorArray[i+2] = 0.0f;
colorArray[i+3] = 1.0f;

} else {

colorArray[i]   = 0.0f;
colorArray[i+1] = 0.0f;
colorArray[i+2] = 0.0f;
colorArray[i+3] = 0.0f;
}
}

Mesh mesh = new Mesh();

mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
mesh.setBuffer(Type.Color,    4, colorArray);
mesh.setBuffer(Type.Index,    3, BufferUtils.createIntBuffer(indexes));
mesh.updateBound();

Geometry geo = new Geometry("ColoredMesh", mesh);

if(voxel.type == BlockType.AIR)geo.setCullHint(CullHint.Always);

Material mat = new Material(main.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setBoolean("VertexColor", true);

/*
* To see the actual rendered quads rather than the wireframe, just comment outthis line.
*/

geo.setMaterial(mat);

attachChild(geo);

this.setCullHint(CullHint.Never);
}
}

``````

WorldManager:

``````package mygame;

import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import java.util.ArrayList;
import java.util.HashMap;

public class WorldManager {

NoiseGenerator noiseGen;
HashMap<Vector3f, Chunk> chunkMap;
ArrayList<Chunk> chunks;

Node rootNode;
Main main;

public WorldManager(Main main) {
this.main = main;
this.rootNode = main.getRootNode();

chunkMap= new HashMap<Vector3f, Chunk>();
chunks = new ArrayList<Chunk>();
noiseGen = new NoiseGenerator(0);
}

public void generate() {

for(int xx = 0; xx < 128; xx+= 64) {
for(int zz = 0; zz < 128; zz+=64) {
Vector3f loc = new Vector3f(xx, 0, zz);
Chunk c = new Chunk(loc, this, main);
chunkMap.put(loc, c);
rootNode.attachChild(c);
}
}

}

}

``````

When I generate only 1 chunk, this get’s rendered:

That’s nice

But, it only renders the top, and two adjacent sides:

the other two sides just don’t exist:

And that’s a problem, cause when I generate more chunks, it makes something like this:

Anyone know what’s going on?
Sorry for my huge wall of text

have my custom voxel lib for destructible parts with greedy meshing, but i didnt test it on big terrain.

It was long time ago, its hard for me now to understand all algorithm again.

Anyway there are other Voxel libs open source that you could compare whats wrong.
based on screenshot i have no idea in what part of algorithm might be the issue.

Hey ox. Could you show me some links? I’ve had zero luck finding what you’re talking about

idk which one, but myself i seen voxel with greedy mesh lib somewhere. You tell you cant find i just type in github search and here it is:

i remember one had greedy meshing code, so you could compare. Just need find which one it was.

And here is my code i have for greedy mesh:

``````public class GreedyMeshGenerator {

private VoxelChunk chunk;

public GreedyMeshGenerator(VoxelChunk chunk) {
this.chunk = chunk;
}

public MeshData generate(int levelOfDetail) {
long timeStart = System.currentTimeMillis();
int w;
int h;
int l;
int k;
GreedyMeshFaceData[][][] greedyGrid = new GreedyMeshFaceData[6][][];
/*
* Generate map of layers for each direction
*/
for (int z = 0; z < VoxelChunk.CHUNK_SIZE; z++) {
for (int y = 0; y < VoxelChunk.CHUNK_SIZE; y++) {
for (int x = 0; x < VoxelChunk.CHUNK_SIZE; x++) {
VoxelBlock element = chunk.voxel.getBlockFastAccess(chunk.loc.add(x, y, z));
if (element != null) {
if (chunk.isChanged) {
}
for (FaceEnum face : element.faces) {
int layerIndex = 0;
int innerLayerCoords = 0;
if (face.equals(FaceEnum.Y)) {
layerIndex = y;
innerLayerCoords = z * VoxelChunk.CHUNK_SIZE + x;
} else if (face.equals(FaceEnum.YNEG)) {
layerIndex = y;
innerLayerCoords = z * VoxelChunk.CHUNK_SIZE + x;
} else if (face.equals(FaceEnum.X)) {
layerIndex = x;
innerLayerCoords = y * VoxelChunk.CHUNK_SIZE + z;
} else if (face.equals(FaceEnum.XNEG)) {
layerIndex = x;
innerLayerCoords = y * VoxelChunk.CHUNK_SIZE + z;
} else if (face.equals(FaceEnum.Z)) {
layerIndex = z;
innerLayerCoords = y * VoxelChunk.CHUNK_SIZE + x;
} else if (face.equals(FaceEnum.ZNEG)) {
layerIndex = z;
innerLayerCoords = y * VoxelChunk.CHUNK_SIZE + x;
}
if (greedyGrid[face.getIndex()] == null) {
greedyGrid[face.getIndex()] = new GreedyMeshFaceData[VoxelChunk.CHUNK_SIZE][];
}
if (greedyGrid[face.getIndex()][layerIndex] == null) {
greedyGrid[face.getIndex()][layerIndex] = new GreedyMeshFaceData[VoxelChunk.CHUNK_SIZE * VoxelChunk.CHUNK_SIZE];
}
greedyGrid[face.getIndex()][layerIndex][innerLayerCoords] = new GreedyMeshFaceData(element.textureType, face);
}
}
}
}
}
//printSideAscii(greedyGrid, FaceEnum.Y.getIndex());
//printSideAscii(greedyGrid, FaceEnum.YNEG.getIndex());
//printSideAscii(greedyGrid, FaceEnum.X.getIndex());
//printSideAscii(greedyGrid, FaceEnum.XNEG.getIndex());
//printSideAscii(greedyGrid, FaceEnum.Z.getIndex());
//printSideAscii(greedyGrid, FaceEnum.ZNEG.getIndex());
//List<FaceData> faces = new ArrayList<>();
MeshData newMeshData = new MeshData(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE); //todo niech wysyła tyle ile musi
for (int direction = 0; direction < greedyGrid.length; direction++) {
GreedyMeshFaceData[][] directionMap = greedyGrid[direction];
if (directionMap != null) {
for (int layer = 0; layer < directionMap.length; layer++) {
GreedyMeshFaceData[] layerMap = directionMap[layer];
if (layerMap != null) {
//System.out.println("for direction: " + direction + " and layer: " + layer);
int n = 0;
for (int y = 0; y < VoxelChunk.CHUNK_SIZE; y++) {
for (int x = 0; x < VoxelChunk.CHUNK_SIZE;) {
GreedyMeshFaceData currentFaceData = layerMap[n];
if (n < layerMap.length && currentFaceData != null) {
/*
* Compute the width
*/
for (w = 1; x + w < VoxelChunk.CHUNK_SIZE && layerMap[n + w] != null && currentFaceData.type == layerMap[n + w].type; w++) {
}
/*
* Compute the height
*/
boolean done = false;
for (h = 1; y + h < VoxelChunk.CHUNK_SIZE; h++) {
for (k = 0; k < w; k++) {
if (layerMap[n + k + h * VoxelChunk.CHUNK_SIZE] == null || currentFaceData.type != layerMap[n + k + h * VoxelChunk.CHUNK_SIZE].type) {
done = true;
break;
}
}
if (done) {
break;
}
}
GreedyMeshFaceData faceData = new GreedyMeshFaceData(currentFaceData.type, currentFaceData.face);
faceData.x = x;
faceData.y = y;
faceData.w = w;
faceData.h = h;
for (l = 0; l < h; ++l) {
for (k = 0; k < w; ++k) {
layerMap[n + k + l * VoxelChunk.CHUNK_SIZE] = null;
}
}
//////////////////////////////////////////////////////////
Vector3f sizeMultiplier = new Vector3f(1, 1, 1);
Vector3f locationOffset = new Vector3f(0, 0, 0);
float texOffsetX = x;
float texOffsetY = y;
float texScaleX = x;
float texScaleY = y;
if(direction == 4 || direction == 5){
sizeMultiplier = new Vector3f(w, h, 1);
locationOffset = new Vector3f(x, y, layer);
texOffsetX = x;
texOffsetY = y;
texScaleX = w;
texScaleY = h;
}
if(direction == 2 || direction == 3){
sizeMultiplier = new Vector3f(1, h, w);
locationOffset = new Vector3f(layer, y, x);
texOffsetX = x;
texOffsetY = y;
texScaleX = w;
texScaleY = h;
}
if(direction == 0 || direction == 1){
sizeMultiplier = new Vector3f(w, 1, h);
locationOffset = new Vector3f(x, layer, y);
texOffsetX = x;
texOffsetY = y;
texScaleX = w;
texScaleY = h;
}
int globalChunkIndex = newMeshData.verts.size();
for (Vector3f baseVert : faceData.face.getBaseVerts()) {
//System.out.println("vert data total loc: " + baseVert.mult(sizeMultiplier).mult(1).add(chunk.loc.x, chunk.loc.y, chunk.loc.z).add(locationOffset) + " size multiplier: " + sizeMultiplier + " chunk loc: " + new Vector3f(chunk.loc.x, chunk.loc.y, chunk.loc.z) + " locationOffset: " + locationOffset);
}
//System.out.println("Generated Face true loc: " + new Vector3f(chunk.loc.x, chunk.loc.y, chunk.loc.z).add(locationOffset));
for (int[] texOffset : faceData.face.getTexturePositions()) {
newMeshData.textureCoords.add(getTextureCoords(((float) texOffset[0] * texScaleX) + texOffsetX, ((float) texOffset[1] * texScaleY) + texOffsetY));
//System.out.println("offset: " + offsetLoc);
//System.out.println("x: " + (((float)texOffset[0] / 10) + texOffsetX / 10) + " y: " + (((float)texOffset[1] / 10) + texOffsetY / 10));
}
for (int index : faceData.face.getBaseIndexes()) {
}
x += w;
n += w;
} else {
x++;
n++;
}
}
}
}
}
}
}
//        for (GreedyMeshFaceData face : faces) {
//            System.out.println("Generated Face: " + face);
//        }
//System.out.println("greedymesh generate time: " + (System.currentTimeMillis() - timeStart));
return newMeshData;
}

private static Vector2f getTextureCoords(float x, float y) {
return new Vector2f(x, y);
}

private void printSideAscii(FaceEnum[][][] greedyGrid, int direction) {
System.out.println("printSideAscii direction: " + direction);
FaceEnum[][] directionMap = greedyGrid[direction];
if(directionMap != null) {
for (int layer = 0; layer < directionMap.length; layer++) {
FaceEnum[] layerMap = directionMap[layer];
int n = 0;
System.out.println("layer: " + layer);
if(layerMap != null) {
for (int y = 0; y < VoxelChunk.CHUNK_SIZE; y++) {
for (int x = 0; x < VoxelChunk.CHUNK_SIZE; x++) {
System.out.print(layerMap[n] != null ? " o" : " x");
n++;
}
System.out.println();
}
}
}
}
}
}
``````
1 Like

Hey ox. All these greedy mesh implementations are really complex and seem to be dependent on other variables of the project. I can barely understand how to use then tbh. Do you have something independent like roboleary’s one

hi, like told on Discord, i would write here for topic.

You can see algorithm description here:

there are other sited that explain it like:

or

or some implementations like:

noone really help me in understanding it, i just look google and learn myself.

It was long time ago, but basically you just go layer by layer in each X,Y,Z directions and for each layer you go from y to x or opposite looking for larger areas. simple as that.

wow i am a super beginner.

I’m completely depending on other people’s code to make the algorithm unfortunately.

I know I’m being tedious and i apologize for that, but can you drop some code snippets as to how to “look for larger areas”? Also how do you build the mesh of quads?

Note: I’ve made an entire block world game without greedy meshing (http://mythruna.com/). You can get quite far with much simpler approaches and then switch later.

…the side-benefit is that you won’t have to rewrite your greedy meshing code every time you add a new type of vertex attribute. For example, block worlds often use baked lighting and when you add that you’ll need to consider ‘unique vertexes’ in a different way. Each new thing like this you add to a vertex is going to have to be factored back into your greedy mesher.

Or just ignore this performance improvement until you already have a game.

1 Like

Just in case you are such a beginner that you don’t know the non-greedy algorithm for block worlds, it’s straight forward:

``````for every cell {
if it's empty skip it
for every neighboring cell {
if it's empty, emit a quad
}
}
batch the quads into a mesh
``````

And if implementing that simple algorithm is beyond your current skill then just use one of the already built and fully working block libraries.

“look for larger areas” is explained here:

you just expand it to work in 3d

I actually got the greedy thing working! but, i have a rather dumb question
what is baked lighting and how can i use it
is it the thing where every voxel has a light level like in minecraft?

1 Like

I’ll also try to implement the pseudo pseudo code you gave me if greedy causes too many issues

Flood Fill Lightning is term you search. as i remember Its similar how you do 2d pathfinding, but for 3d. You just go from light source and flood all “free” voxel areas. The more far it go, the less light.