Cubes – A Block World Framework [Update Preview]

What about a link to the source code with the examples? When I download the plugin I only got the compiled code.
Besides you could only generate meshes when they are near the camera. In your examples we only have a fistful of chunks but i think most people want to create large terrains.
One last question: do you want to add shadow baking? Post processor shadow does not seem to be the best way for the performance and without light and shadow everything looks a bit flat.

What about a link to the source code with the examples? When I download the plugin I only got the compiled code.
Besides you could only generate meshes when they are near the camera. In your examples we only have a fistful of chunks but i think most people want to create large terrains.
One last question: do you want to add shadow baking? Post processor shadow does not seem to be the best way for the performance and without light and shadow everything looks a bit flat.

What about a link to the source code with the examples? When I download the plugin I only got the compiled code.
Besides you could only generate meshes when they are near the camera. In your examples we only have a fistful of chunks but i think most people want to create large terrains.
One last question: do you want to add shadow baking? Post processor shadow does not seem to be the best way for the performance and without light and shadow everything looks a bit flat.

Hm, I have some problems posting this message

Yes, I will definitely post the source code to the wiki or if I can manage it, add it to the plugin. The framework is / will be completely open-source. :slight_smile:

About your suggestions - Great ideas. :slight_smile:
Chunks near the camera: I think, I’ll add write control that can be added to the terrain to automatically hide chunks at a specified distance.
Shadow baking: Yes, this is definitely on my list, paired with light calculations so glowing blocks etc. are possible (wihtout using jME’s light which cause the complete scene to render again). Maybe I can modify the texture procedurally to achieve those effects.

You can add the source and javadoc to the plugin just by tweaking the settings in the projects.

Hi, what about modyfing the MeshOptimizer and add a method to generate a single Block Mesh like this:
[java]
public class MeshOptimizer
{
private static Vector3f[] vertices;
private static Vector2f[] textureCoordinates;
private static int[] indices;
private static ArrayList verticeList = new ArrayList();
private static ArrayList textureCoordinateList = new ArrayList();
private static ArrayList indicesList = new ArrayList();

private static final BlockChunk_MeshMerger defaultBlockMerger = new BlockChunk_MeshMerger()
{
public boolean shouldFaceBeAdded(BlockChunk chunk, Vector3Int location, Block.Face face) {
BlockType neighborBlock = chunk.getNeighborBlock(location, face);
if (neighborBlock != null)
{
return neighborBlock.getSkin().isTransparent() != neighborBlock.getSkin().isTransparent();
}

return true;
}
};

public static Mesh generateOptimizedMesh(BlockChunk blockChunk)
{
loadMeshData(blockChunk, defaultBlockMerger);
return generateMesh();
}

public static Mesh generateSingleBlock(Class blockClass)
{
verticeList = new ArrayList();
textureCoordinateList = new ArrayList();
indicesList = new ArrayList();

loadSingleMesh(new Vector3f(0,0,0), new Vector3Int(0,0,0), BlockManager.getType(blockClass).getSkin(),defaultBlockMerger,null);

vertices = new Vector3f[verticeList.size()];
for (int i = 0; i < verticeList.size(); i++) {
vertices[i] = ((Vector3f)verticeList.get(i));
}
textureCoordinates = new Vector2f[textureCoordinateList.size()];
for (int i = 0; i < textureCoordinateList.size(); i++) {
textureCoordinates[i] = ((Vector2f)textureCoordinateList.get(i));
}
indices = new int[indicesList.size()];
for (int i = 0; i < indicesList.size(); i++)
indices[i] = ((Integer)indicesList.get(i)).intValue();

return generateMesh();
}

private static void loadMeshData(BlockChunk chunk, BlockChunk_MeshMerger meshMerger) {
verticeList = new ArrayList();
textureCoordinateList = new ArrayList();
indicesList = new ArrayList();
Vector3Int tmpLocation = new Vector3Int();
for (int x = 0; x < CubesSettings.CHUNK_SIZE_X; x++) {
for (int y = 0; y < CubesSettings.CHUNK_SIZE_Y; y++) {
for (int z = 0; z < CubesSettings.CHUNK_SIZE_Z; z++) {
tmpLocation.set(x, y, z);
BlockType block = chunk.getBlock(tmpLocation);
if (block != null) {
BlockSkin blockSkin = block.getSkin();
Vector3f blockLocation = new Vector3f(x, y, z);

loadSingleMesh(blockLocation,tmpLocation,blockSkin,meshMerger,chunk);
}
}
}
}

vertices = new Vector3f[verticeList.size()];
for (int i = 0; i < verticeList.size(); i++) {
vertices[i] = ((Vector3f)verticeList.get(i));
}
textureCoordinates = new Vector2f[textureCoordinateList.size()];
for (int i = 0; i < textureCoordinateList.size(); i++) {
textureCoordinates[i] = ((Vector2f)textureCoordinateList.get(i));
}
indices = new int[indicesList.size()];
for (int i = 0; i < indicesList.size(); i++)
indices[i] = ((Integer)indicesList.get(i)).intValue();

verticeList = null;
textureCoordinateList = null;
indicesList = null;
}

private static void loadSingleMesh(Vector3f blockLocation, Vector3Int tmpLocation, BlockSkin blockSkin,BlockChunk_MeshMerger meshMerger,BlockChunk chunk)
{
Vector3f faceLoc_Bottom_TopLeft = blockLocation.add(new Vector3f(0.0F, 0.0F, 0.0F)).mult(CubesSettings.BLOCK_SIZE);
Vector3f faceLoc_Bottom_TopRight = blockLocation.add(new Vector3f(1.0F, 0.0F, 0.0F)).mult(CubesSettings.BLOCK_SIZE);
Vector3f faceLoc_Bottom_BottomLeft = blockLocation.add(new Vector3f(0.0F, 0.0F, 1.0F)).mult(CubesSettings.BLOCK_SIZE);
Vector3f faceLoc_Bottom_BottomRight = blockLocation.add(new Vector3f(1.0F, 0.0F, 1.0F)).mult(CubesSettings.BLOCK_SIZE);
Vector3f faceLoc_Top_TopLeft = blockLocation.add(new Vector3f(0.0F, 1.0F, 0.0F)).mult(CubesSettings.BLOCK_SIZE);
Vector3f faceLoc_Top_TopRight = blockLocation.add(new Vector3f(1.0F, 1.0F, 0.0F)).mult(CubesSettings.BLOCK_SIZE);
Vector3f faceLoc_Top_BottomLeft = blockLocation.add(new Vector3f(0.0F, 1.0F, 1.0F)).mult(CubesSettings.BLOCK_SIZE);
Vector3f faceLoc_Top_BottomRight = blockLocation.add(new Vector3f(1.0F, 1.0F, 1.0F)).mult(CubesSettings.BLOCK_SIZE);

if (chunk == null || meshMerger.shouldFaceBeAdded(chunk, tmpLocation, Block.Face.Top)) {
addVerticeIndexes(verticeList, indicesList);
verticeList.add(faceLoc_Top_BottomLeft);
verticeList.add(faceLoc_Top_BottomRight);
verticeList.add(faceLoc_Top_TopLeft);
verticeList.add(faceLoc_Top_TopRight);
addBlockTextureCoordinates(textureCoordinateList, blockSkin.getTextureLocation(chunk, tmpLocation, Block.Face.Top));
}
if (chunk == null || meshMerger.shouldFaceBeAdded(chunk, tmpLocation, Block.Face.Bottom)) {
addVerticeIndexes(verticeList, indicesList);
verticeList.add(faceLoc_Bottom_BottomRight);
verticeList.add(faceLoc_Bottom_BottomLeft);
verticeList.add(faceLoc_Bottom_TopRight);
verticeList.add(faceLoc_Bottom_TopLeft);
addBlockTextureCoordinates(textureCoordinateList, blockSkin.getTextureLocation(chunk, tmpLocation, Block.Face.Bottom));
}
if (chunk == null || meshMerger.shouldFaceBeAdded(chunk, tmpLocation, Block.Face.Left)) {
addVerticeIndexes(verticeList, indicesList);
verticeList.add(faceLoc_Bottom_TopLeft);
verticeList.add(faceLoc_Bottom_BottomLeft);
verticeList.add(faceLoc_Top_TopLeft);
verticeList.add(faceLoc_Top_BottomLeft);
addBlockTextureCoordinates(textureCoordinateList, blockSkin.getTextureLocation(chunk, tmpLocation, Block.Face.Left));
}
if (chunk == null || meshMerger.shouldFaceBeAdded(chunk, tmpLocation, Block.Face.Right)) {
addVerticeIndexes(verticeList, indicesList);
verticeList.add(faceLoc_Bottom_BottomRight);
verticeList.add(faceLoc_Bottom_TopRight);
verticeList.add(faceLoc_Top_BottomRight);
verticeList.add(faceLoc_Top_TopRight);
addBlockTextureCoordinates(textureCoordinateList, blockSkin.getTextureLocation(chunk, tmpLocation, Block.Face.Right));
}
if (chunk == null || meshMerger.shouldFaceBeAdded(chunk, tmpLocation, Block.Face.Front)) {
addVerticeIndexes(verticeList, indicesList);
verticeList.add(faceLoc_Bottom_BottomLeft);
verticeList.add(faceLoc_Bottom_BottomRight);
verticeList.add(faceLoc_Top_BottomLeft);
verticeList.add(faceLoc_Top_BottomRight);
addBlockTextureCoordinates(textureCoordinateList, blockSkin.getTextureLocation(chunk, tmpLocation, Block.Face.Front));
}
if (chunk == null || meshMerger.shouldFaceBeAdded(chunk, tmpLocation, Block.Face.Back)) {
addVerticeIndexes(verticeList, indicesList);
verticeList.add(faceLoc_Bottom_TopRight);
verticeList.add(faceLoc_Bottom_TopLeft);
verticeList.add(faceLoc_Top_TopRight);
verticeList.add(faceLoc_Top_TopLeft);
addBlockTextureCoordinates(textureCoordinateList, blockSkin.getTextureLocation(chunk, tmpLocation, Block.Face.Back));
}
}

private static void addBlockTextureCoordinates(ArrayList textureCoordinatesList, BlockSkin_TextureLocation textureLocation)
{
textureCoordinatesList.add(getTextureCoordinates(textureLocation, 0, 0));
textureCoordinatesList.add(getTextureCoordinates(textureLocation, 1, 0));
textureCoordinatesList.add(getTextureCoordinates(textureLocation, 0, 1));
textureCoordinatesList.add(getTextureCoordinates(textureLocation, 1, 1));
}

private static Vector2f getTextureCoordinates(BlockSkin_TextureLocation textureLocation, int xUnitsToAdd, int yUnitsToAdd) {
float textureCount = 16.0F;
float textureUnit = 1.0F / textureCount;
float x = (textureLocation.getColumn() + xUnitsToAdd) * textureUnit;
float y = (-1 * textureLocation.getRow() + (yUnitsToAdd - 1)) * textureUnit + 1.0F;
return new Vector2f(x, y);
}

private static void addVerticeIndexes(ArrayList verticeList, ArrayList indexesList) {
int verticesCount = verticeList.size();
indexesList.add(Integer.valueOf(verticesCount + 2));
indexesList.add(Integer.valueOf(verticesCount + 0));
indexesList.add(Integer.valueOf(verticesCount + 1));
indexesList.add(Integer.valueOf(verticesCount + 1));
indexesList.add(Integer.valueOf(verticesCount + 3));
indexesList.add(Integer.valueOf(verticesCount + 2));
}

private static Mesh generateMesh() {
Mesh mesh = new Mesh();
mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoordinates));
mesh.setBuffer(VertexBuffer.Type.Index, 1, BufferUtils.createIntBuffer(indices));
mesh.updateBound();
return mesh;
}
}[/java]

Now I am able to simply create Boxes and display them on the screen, this could be useful for an inventory:
[java]
public void simpleInitApp() {
com.cubes.CubesSettings.ASSET_MANAGER = this.assetManager;
com.cubes.test.CubesTestAssets.registerBlocks();

BlockManager.register(Block_Grass.class, new BlockSkin(new BlockSkin_TextureLocation[] { new BlockSkin_TextureLocation(0, 0), new BlockSkin_TextureLocation(1, 0), new BlockSkin_TextureLocation(2, 0) }, false)
{
protected int getTextureLocationIndex(BlockChunk chunk, Vector3Int blockLocation, Block.Face face)
{
if (chunk == null || chunk.isBlockOnSurface(blockLocation)) {
switch (face) {
case Top:
return 0;
case Bottom:
return 2;
}
return 1;
}
return 2;
}
});

Geometry box = generateBox(assetManager,Block_Wood.class);
box.move(1, -1.5f, 1);
box.rotate(.4f, .4f, 0f);
box.scale(0.5f, 0.5f, 0.5f);

Geometry box2 = generateBox(assetManager,Block_Grass.class);
box2.move(1, 1.5f, 1);
box2.rotate(.4f, .4f, 0f);
box2.scale(0.5f, 0.5f, 0.5f);

rootNode.attachChild(box);
rootNode.attachChild(box2);

flyCam.setMoveSpeed(50.0F);
}

private Geometry generateBox(AssetManager assetManager,Class blockClass)
{
Geometry box = new Geometry();
Material mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
Texture texture = CubesSettings.ASSET_MANAGER.loadTexture(CubesSettings.BLOCK_TEXTURE_PATH);
texture.setMagFilter(Texture.MagFilter.Nearest);
texture.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
mat.setTexture(“ColorMap”, texture);
box.setMaterial(mat);

Mesh m = MeshOptimizer.generateSingleBlock(blockClass);
m.updateBound();
box.setMesh(m);

return box;
} [/java]

It’s basically a good idea - I think, I’ll add a functionality to export a single block as geometry.
But not in the MeshOptimizer class, it should only handle whole chunks, where optimation is needed.

Thanks for the hint. :slight_smile:

@destroflyer said: New Feature: Serialization The block terrain is now serializable to a byte[] array (or to a custom bitstream), which can be be sended over the network or saved in a file to reproduce the terrain. :)

Documentation: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:contributions:cubes:tools:serialization

Moreover, a few little utility methods in classes like Vector3Int (clone, equals, etc.) were added. Please note, that the plugin is still under development - If you guys have any suggestions for improving the framework, please tell me.


Uhm, why don’t you just use jME’s serialization functions? Make your block generator a control and do that in the write() method.

It looks cool. Is the water also made of blocks or is it just the water filter?

Another Question:
Evertime we change 1 Box the Chunk generates a new Mesh. I think it would be better to modify the existing Mesh when the change is so small.
Or when you do not do this you could merge some Rectangles and repeat the texture map at that position to improve the performance.
I overlooked something? What are your plans for the future?

@ogerlord said: Another Question: Evertime we change 1 Box the Chunk generates a new Mesh. I think it would be better to modify the existing Mesh when the change is so small. Or when you do not do this you could merge some Rectangles and repeat the texture map at that position to improve the performance. I overlooked something? What are your plans for the future?

From experience, it is very hard to modify the existing mesh because a removal of a block can add more quads than you have space for and adding a block can also… or remove a bunch of quads. It’s unpredictable. If chunk sizes are reasonable then generation will be faster than anything smart you try to do with reuse, I think.

1 Like

Hi, first thank you so much for this amzing framework! I really like playing with it :slight_smile:

But currently I’d like to know the height of a cube at given coordinates, I can’t figure out how to do that.

Could you help me please ? Thank you.

EDIT: By the way, I used noise for my terrain’s generation.

You could do something like “check location(x, 0, z) - if a block exists, y = y + 1 - check again, … until you reach the surface”.
It would be a very slow technique though, I was thinking about other solutions but it all depends on what you interprete as “height”.

Maybe I could something smarter internally when the terrain is modified, so you can retrieve these values directly. Thanks for the hint. :slight_smile:
(Unluckily, the next weeks are full of exams at the university, so it will take some time I guess)

Ok thanks I tried something like that and it seems to work.
I use this for my player spawn location so I only use it once and this is not really slow.

Here is the code I use:
[java]
public int getHeight(int x, int z)
{
int height = 0;
for(int i = 0; i < 256; i++)
{
if(terrain.getBlock(new Vector3Int(x, i, z)) != null)
{
height++;
}
}
return height;
}[/java]

Do you care about caves/overhangs in your definition of “height”?

You might need to scan down from the top looking for non-empty.

1 Like

His code does care: it scans all blocks from bottom to top, remembering the last nonempty one that it hit.
He might shave off some CPU cycles by looking from top to bottom and doing a break statement on the first nonempty block. But since it’s just 256 lookups done only on the rare respawn, I would think that it’s not an issue worth revisiting.

Yes I didn’t think about the caves. And you’re right this would be better by starting from the top.
By the way, it allowed me to generate a little forest :

1 Like

Wow, looks cool - I’m really glad, the plugin works. :slight_smile:
Your application reminds me of something, I totally forgot: “Macroblocks”, as I call them. You’ll be able to define a structure, e.g. a tree or a house and then insert it at a given position. This should make creating worlds a lot easier.

I did a little class called TreesManager which allowed me to generate a given number of trees in a square. Currently it doesn’t check if a tree already exists when spawning an other one, i’ll fix this later.

I just noticed something: I can walk through my trees :lol:

Do you know how I could “update” the physics shape ?

You can replace the current RigidBodyControl with a new one. It should be based on the new mesh then.
Maybe there’s a better way to update the physics collision shape, but I never found one. :smiley: