I ended up following @remy_vd suggestion and modifying Blocks to add an extra row around the edge of every Chunk. For a 32x32x32 Chunk, this turns out to be around a 20% increase in the number of Blocks. I can leave triggerAdjacentChunkUpdates
off, so Chunk generation is as fast as before. The UI also feels more responsive, as there should now be no ‘wasted quads’.
As an aside, the Blocks are stored in an array of object references. This requires 4 bytes per block.
Using an index scheme with shorts as keys would halve the memory usage to 2 bytes per Block, and still allow up to 65536 different Blocks.
@remy_vd How to get Chunk from BlockBuilder.java?
You can use the ChunkManager#getChunk
method. Why do you need the chunk? A chunk is more like a behind the scenes way of structuring the blocks.
You most likely want to add, remove, update blocks without caring about the chunk management behind it.
All methods to handle blocks are available in the ChunkManager
:
- addBlock
- removeBlock
- getBlock
- getNeighbourBlock
- …
Thanks, I understand how I would do it
I want to save it in a file FileRepository#save(Chunk chunk)
Hi all,
My goal is to build a game running on Android and allowing my children to build their world
Since the target platform is a device with limited GPU capabilities, I spent hours to make some optimizations :
- use a texture atlas to limit the number of shader and texture switches to the bare minimum
- build a new custom shader that supports the atlas (with Ambient Occlusion and scrolling textures)
- abandon the current lighting implementation in favor of Minecraft-like lightning
- optimized code base :
- heavy rework of the chunkmanager and the two pagers
- solved the triggerAdjacentChunkUpdates issue by prefetching neighbour chunks before rendering
- optimized code parts that were heavily used (related essentially to chunk meshing)
There is still room for optimization. I’d like to reduce the number of triangles thanks to greedy meshing, but it is not easy with the way the mesh is currently generated (and combined with AO). I’ll probably limit that to only cube shapes.
I also implemented some basic water simulation.
I’ll try to contribute all of this to the code base, although it is becoming difficult due to the amount of changes and due to some peculiarities of my projet.
If you see priorities among all of these changes, please tell me.
Thanks Remy for the codebase !
A couple of screenshots :
Minecraft-like lightning with Ambiant Occlusion (a light deep in the cave):
A single texture atlas is used for all blocks (including the water) :
Lighting during the night :
Basic water simulation :
looks good!
awesome work @vxel ! I welcome all contributions and would love to have a chat with you to see what and how we can integrate your optimisations in Blocks.
The water simulation is something I read a lot about and I actually created a working POC but never got around implementing it. It looks really smooth, great job!
Thanks for your replies.
I will try to extract from my code a couple of quick-wins regarding optimisations. No problem to have a chat about that.
A first optimisation quick-win is certainly to pre-compute the quaternions used by the getRotationFromDirection(), getYawFromDirection() and getFaceDirection() in the Shape class. These methods are heavily used during the mesh creation and are really expensives. I will prepare a patch for that if you agree.
Regarding the water simulation, I created a new shape, called ‘Liquid’ with a height parameter (i.e. the water/lava/… level). This shape has the property to adapt its up face corners to the surrounding liquid blocks.
I next created a couple of blocks using this shape with different heights (the levels).
I implemented a ChunkLiquidManager that performs the water simulation by propagating the water blocks, and a ChunkLiquidManagerState that triggers the simulation steps (at the configured speed) and the requests to mesh of the updated chunks.
The liquid simulation is really basic : it first tries to progate downward, adding full liquid blocks. If it can’t (due to the presence of a block), it then propagates horizontally, lowering the water level, until the level is 0. That seems sufficient for a first try for my children
I pushed version 1.7.0 of Blocks.
The most notable changes are:
- Added an additional initialize method on BlocksConfig to skip the registration of default blocks:
BlocksConfig.initialize(assetManager, false)
- thanks @Ali_RS - Performance improvements by storing and reusing Quaternions() in the mesh calculation - thanks @vxel
- Added a new Cylinder shape block
thee different cylinder blocks, from left to right:
- a birch cylinder block with 12 radial samples pointing up
- a stone cylinder/cone block with 12 radial samples and a different radius for top (0.2f) and bottom (0.5f), pointing east
- an oak cylinder block with 32 radial samples and a smal radius (0.3f)
Shape defaultCylinder = new Cylinder();
Shape cone = new Cylinder(Direction.EAST, 0.2f, 0.5f, 12);
Shape pole = new Cylinder(Direction.UP, 0.3f, 0.3f, 32);
The release notes can be found on github:
@remy_vd Regarding your work on the fluid simulation, did you use my algorithm? If you made improvements and/or corrections I would be interested
Btw, I’m not plainly satisfied with the way my algo works for removing water. Did you already take a look at this subject ?
No, I did not reuse your algorithm, but as both approaches use cellular automata the idea behind and therefore the implementation is similar.
There is a LiquidManager
that processes a queue of Liquid Action’s. An action can be either a flow or drain action. When a liquid block is placed or removed, a new flow or drain action is put on the queue. When a none-liquid block is placed or removed, a flow or drain action is triggered on all the liquid block neighbours of this block.
So if you place a block next to a liquid block, a flow action is triggered on that liquid block.
The LiquidManager processes the entire queue of actions on each simulation step. For a flow action, the liquid first tries to move to a block below. When this succeeds, a new flow action is created for the block below, placed on the queue and the processing for this block is done. When the liquid is unable to move to a block below, it tries to move horizontally to the 4 neighbour blocks: north, east, south and west. A new flow action is created and placed on the queue for each direction the liquid can flow.
The liquid level of a block is lowered each time the liquid flows horizontally. When the liquid flows down, the liquid level of the block is reset.
For a drain action, the liquid block is removed and a new drain action is placed on the queue for each neighbour that has a lower liquid level. As the neighbour has a lower level, it means we are the source of the neighbour.
When a neighbour has a higher liquid level, a flow action is placed on the queue. That neighbour will be able to flow into the free space.
Thanks for your explanations. It seems our algorithms are actually the same .
BTW, like in Ialon, do you intend to support in Blocks flood lightning (i.e. minecraft-like lightning) ? This feature seems to me a must-have for a block framework , although this requires a bunch of changes…