LiteCraft/src/main/java/com/halotroop/litecraft/world/Chunk.java

246 lines
7.9 KiB
Java

package com.halotroop.litecraft.world;
import java.util.*;
import java.util.function.ToIntFunction;
import org.joml.Vector3f;
import com.halotroop.litecraft.logic.SODSerializable;
import com.halotroop.litecraft.render.BlockRenderer;
import com.halotroop.litecraft.types.block.*;
import com.halotroop.litecraft.world.gen.WorldGenConstants;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.*;
import tk.valoeghese.sod.*;
public class Chunk implements BlockAccess, WorldGenConstants, SODSerializable
{
/** @param x in-chunk x coordinate.
* @param y in-chunk y coordinate.
* @param z in-chunk z coordinate.
* @return creates a long that represents a coordinate, for use as a key in arrays. */
public static int index(int x, int y, int z)
{ return (x & MAX_POS) | ((y & MAX_POS) << POS_SHIFT) | ((z & MAX_POS) << DOUBLE_SHIFT); }
private final Block[] blocks = new Block[CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE];
private BlockInstance[] blockInstances = new BlockInstance[CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE];
private boolean shouldRender = false;
public final int chunkX, chunkY, chunkZ;
public final int chunkStartX, chunkStartY, chunkStartZ;
private boolean fullyGenerated = false;
public final int dimension;
private boolean dirty = true;
/** A holder for the rendered blocks in this chunk. This array is *NOT* safe to use for getting BIs at a position!
* It can vary in size from 0 to 512 elements long and must only be read linearly. */
private BlockInstance[] renderedBlocks = new BlockInstance[CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE];
public Chunk(World world, int chunkX, int chunkY, int chunkZ, int dimension)
{
this.chunkX = chunkX;
this.chunkY = chunkY;
this.chunkZ = chunkZ;
this.chunkStartX = chunkX << POS_SHIFT;
this.chunkStartY = chunkY << POS_SHIFT;
this.chunkStartZ = chunkZ << POS_SHIFT;
this.dimension = dimension;
}
public boolean doRender()
{ return this.shouldRender; }
public void setFullyGenerated(boolean fullyGenerated)
{ this.fullyGenerated = fullyGenerated; }
@Override
public Block getBlock(int x, int y, int z)
{
if (x > CHUNK_SIZE || y > CHUNK_SIZE || z > CHUNK_SIZE || x < 0 || y < 0 || z < 0)
{ throw new RuntimeException("Block [" + x + ", " + y + ", " + z + "] out of chunk bounds!"); }
return blocks[index(x, y, z)];
}
public BlockInstance getBlockInstance(int x, int y, int z)
{
if (x > CHUNK_SIZE || y > CHUNK_SIZE || z > CHUNK_SIZE || x < 0 || y < 0 || z < 0)
{ throw new RuntimeException("BlockInstance [" + x + ", " + y + ", " + z + "] out of chunk bounds!"); }
return this.blockInstances[index(x, y, z)];
}
public void render(BlockRenderer blockRenderer)
{
if (shouldRender)
{
if (dirty)
{
dirty = false;
List<BlockInstance> tempList = new ArrayList<>();
Arrays.fill(renderedBlocks, null);
for (int x = 0; x < CHUNK_SIZE; x++)
for (int y = 0; y < CHUNK_SIZE; y++)
for (int z = 0; z < CHUNK_SIZE; z++)
{
BlockInstance block = getBlockInstance(x, y, z);
// Check for chunk edges to avoid errors when get the neighboring blocks, TODO fix this
if (x == 0 || x == CHUNK_SIZE - 1 || z == 0 || z == CHUNK_SIZE - 1 || y == 0 || y == CHUNK_SIZE - 1)
{
tempList.add(block);
continue;
}
// Check for air. Yes this is stupid, TODO fix this too
try
{
if (getBlockInstance(x - 1, y, z).getModel() == null || getBlockInstance(x + 1, y, z).getModel() == null ||
getBlockInstance(x, y - 1, z).getModel() == null || getBlockInstance(x, y + 1, z).getModel() == null ||
getBlockInstance(x, y, z - 1).getModel() == null || getBlockInstance(x, y, z + 1).getModel() == null)
{ tempList.add(block); }
}
catch (NullPointerException e)
{ // this seems to be a hotspot for errors
e.printStackTrace(); // so I can add a debug breakpoint on this line
throw e; // e
}
}
renderedBlocks = tempList.toArray(BlockInstance[]::new);
}
blockRenderer.prepareRender();
blockRenderer.render(renderedBlocks);
blockRenderer.shader.stop();
}
}
/** Change the block in this exact position
*
* @param x, y, z The coordinate position of block to overwrite
* @param block The block to place there */
@Override
public void setBlock(int x, int y, int z, Block block)
{
// This section makes sure the blocks don't go out of range
if (x > MAX_POS)
x = MAX_POS;
else if (x < 0) x = 0;
if (y > MAX_POS)
y = MAX_POS;
else if (y < 0) y = 0;
if (z > MAX_POS)
z = MAX_POS;
else if (z < 0) z = 0;
//
this.blocks[index(x, y, z)] = block;
if (this.shouldRender) this.blockInstances[index(x, y, z)] = new BlockInstance(block, new Vector3f(this.chunkStartX + x, this.chunkStartY + y, this.chunkStartZ + z));
dirty = true;
}
/** Set whether or not the chunk should render */
public void setRender(boolean render)
{
if (render && !this.shouldRender) // if it has been changed to true
for (int x = 0; x < CHUNK_SIZE; ++x)
for (int y = 0; y < CHUNK_SIZE; ++y)
for (int z = 0; z < CHUNK_SIZE; ++z)
{
Block block = this.blocks[index(x, y, z)];
this.blockInstances[index(x, y, z)] = new BlockInstance(block,
new Vector3f(
this.chunkStartX + x,
this.chunkStartY + y,
this.chunkStartZ + z));
}
else if (!render && this.shouldRender) // else if it has been changed to false.
// we need to check both variables because there are two cases that make
// the if statement fall to here
blockInstances = new BlockInstance[CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE];
this.shouldRender = render;
dirty = true;
}
public boolean isFullyGenerated()
{ return this.fullyGenerated; }
@Override
public void read(BinaryData data)
{
Int2ObjectMap<Block> palette = new Int2ObjectArrayMap<>();
DataSection paletteData = data.get("palette");
boolean readInt = true; // whether the thing from the palette to be read is int
int intIdCache = 0;
//
for (Object o : paletteData)
{
if (readInt)
{
intIdCache = (int) o;
readInt = false;
}
else
{
palette.put(intIdCache, Block.getBlockOrAir((String) o));
readInt = true;
}
}
//
IntArrayDataSection blockData = data.getIntArray("block");
int index = 0;
// Iterate over each block in the chunk
for (int z = 0; z < CHUNK_SIZE; ++z) // z, y, x order for data saving and loading so we can use incremental pos hashes
for (int y = 0; y < CHUNK_SIZE; ++y)
for (int x = 0; x < CHUNK_SIZE; ++x)
{
blocks[index] = palette.get(blockData.readInt(index));
++index;
}
//
DataSection properties = data.get("properties");
try
{
this.fullyGenerated = properties.readBoolean(0); // index 0 is the "fully generated" property
}
catch (Throwable e)
{
if (!readExceptionNotif)
{
System.out.println("An exception occurred reading properties for a chunk! This could be a benign error due to updates to chunk properties.");
readExceptionNotif = true;
}
}
}
private static boolean readExceptionNotif = false;
private int nextId; // for saving
@Override
public void write(BinaryData data)
{
Object2IntMap<Block> palette = new Object2IntArrayMap<>(); // block to int id
DataSection paletteData = new DataSection();
IntArrayDataSection blockData = new IntArrayDataSection();
int index = 0;
nextId = 0;
ToIntFunction<Block> nextIdProvider = b -> nextId++;
//
for (int z = 0; z < CHUNK_SIZE; ++z) // z, y, x order for data saving and loading so we can use incremental pos hashes
for (int y = 0; y < CHUNK_SIZE; ++y)
for (int x = 0; x < CHUNK_SIZE; ++x)
{
Block b = blocks[index];
blockData.writeInt(palette.computeIntIfAbsent(b, nextIdProvider));
++index;
}
//
palette.forEach((b, id) ->
{
paletteData.writeInt(id);
paletteData.writeString(b.identifier);
});
//
data.put("palette", paletteData);
data.put("block", blockData);
//
DataSection properties = new DataSection();
properties.writeBoolean(this.fullyGenerated);
data.put("properties", properties);
dirty = true;
}
}