Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 31 additions & 12 deletions chunky/src/java/se/llbit/chunky/chunk/BlockPalette.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package se.llbit.chunky.chunk;

import it.unimi.dsi.fastutil.longs.Long2IntMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import se.llbit.chunky.block.*;
import se.llbit.chunky.plugin.PluginApi;
import se.llbit.math.Octree;
Expand Down Expand Up @@ -98,6 +100,19 @@ public void unsynchronize() {
};
}

private final Long2IntMap waterIds = new Long2IntOpenHashMap();
private final Long2IntMap lavaIds = new Long2IntOpenHashMap();

/**
* This method should be called after the chunks were loaded and nothing will be changed about this palette anymore.
*
* It removes intermediate structures.
*/
public void compact() {
waterIds.clear();
lavaIds.clear();
}

/**
* Adds a new block to the palette and returns the palette index.
*
Expand Down Expand Up @@ -163,12 +178,14 @@ public int getWaterId(int level, int data) {
if (level < 0 || level > 15) {
throw new IllegalArgumentException("Invalid water level " + level);
}
CompoundTag tag = new CompoundTag();
tag.add("Name", new StringTag("minecraft:water$chunky"));
tag.add("level", new IntTag(level));
tag.add("data", new IntTag(data));
BlockSpec spec = new BlockSpec(tag);
return put(spec);
long id = (level & 0xF) | ((data & 0xFFFFFFFFL) << 4);
return waterIds.computeIfAbsent(id, key -> {
CompoundTag tag = new CompoundTag();
tag.add("Name", new StringTag("minecraft:water$chunky"));
tag.add("level", new IntTag(level));
tag.add("data", new IntTag(data));
return put(new BlockSpec(tag));
});
}

/**
Expand All @@ -184,12 +201,14 @@ public int getLavaId(int level, int data) {
if (level < 0 || level > 15) {
throw new IllegalArgumentException("Invalid lava level " + level);
}
CompoundTag tag = new CompoundTag();
tag.add("Name", new StringTag("minecraft:lava$chunky"));
tag.add("level", new IntTag(level));
tag.add("data", new IntTag(data));
BlockSpec spec = new BlockSpec(tag);
return put(spec);
long id = (level & 0xFFFFFFFFL) | (data & 0xFFFFFFFFL) << 32;
return lavaIds.computeIfAbsent(id, key -> {
CompoundTag tag = new CompoundTag();
tag.add("Name", new StringTag("minecraft:lava$chunky"));
tag.add("level", new IntTag(level));
tag.add("data", new IntTag(data));
return put(new BlockSpec(tag));
});
}

/**
Expand Down
13 changes: 11 additions & 2 deletions chunky/src/java/se/llbit/chunky/chunk/GenericChunkData.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class GenericChunkData implements ChunkData {
return;
SectionData sectionData = sections.computeIfAbsent(sectionY, SectionData::new);
sectionData.blocks[chunkIndex(x & (X_MAX - 1), y & (SECTION_Y_MAX - 1), z & (Z_MAX - 1))] = block;
sectionData.isEmpty = false;
}

@Override public boolean isBlockOnEdge(int x, int y, int z) {
Expand Down Expand Up @@ -80,7 +81,9 @@ public void addEntity(CompoundTag entity) {
@Override public void clear() {
minSectionY = Integer.MAX_VALUE;
maxSectionY = Integer.MIN_VALUE;
sections.clear();
for (SectionData section : sections.values()) {
section.clear();
}
biomeData.clear();
tileEntities.clear();
entities.clear();
Expand All @@ -98,7 +101,7 @@ public void setBiomeData(BiomeData biomeData) {

@Override
public boolean isEmpty() {
return sections.isEmpty() && entities.isEmpty() && tileEntities.isEmpty();
return entities.isEmpty() && tileEntities.isEmpty() && (sections.isEmpty() || sections.values().stream().allMatch(section -> section.isEmpty));
}

@Override public boolean equals(Object o) {
Expand All @@ -117,6 +120,7 @@ public boolean isEmpty() {
private static class SectionData {
public final int sectionY;
public final int[] blocks;
private boolean isEmpty = true;

public SectionData(int sectionY) {
this(sectionY, new int[X_MAX * SECTION_Y_MAX * Z_MAX]);
Expand All @@ -139,5 +143,10 @@ public SectionData(int sectionY, int[] blocks) {
result = 31 * result + Arrays.hashCode(blocks);
return result;
}

public void clear() {
Arrays.fill(blocks, 0);
isEmpty = true;
}
}
}
2 changes: 2 additions & 0 deletions chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,8 @@ public synchronized void loadChunks(TaskTracker taskTracker, World world, Collec
}
Log.info(String.format("Loaded %d chunks", numChunks));

palette.compact();

isLoading = false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package se.llbit.chunky.renderer.scene.biome;

import it.unimi.dsi.fastutil.longs.Long2IntMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import se.llbit.chunky.world.WorldTexture;
import se.llbit.math.structures.Position2IntStructure;

import java.io.DataInputStream;
import java.io.DataOutputStream;
Expand All @@ -19,6 +22,11 @@ public BiomeStructure load(DataInputStream in) throws IOException {
return new Impl(WorldTexture.load(in));
}

@Override
public Position2IntStructure createIndexStructure() {
return new StructureImpl();
}

@Override
public boolean is3d() {
return false;
Expand Down Expand Up @@ -71,4 +79,20 @@ public float[] get(int x, int y, int z) {
return texture.get(x, z);
}
}

private static class StructureImpl implements Position2IntStructure {
private final Long2IntMap biomeMap = new Long2IntOpenHashMap();

@Override
public void set(int x, int y, int z, int data) {
long key = ((long) x >> 4) << 32 | ((z >> 4) & 0xffffffffL);
biomeMap.put(key, data);
}

@Override
public int get(int x, int y, int z) {
long key = ((long) x >> 4) << 32 | ((z >> 4) & 0xffffffffL);
return biomeMap.get(key);
}
}
}
19 changes: 14 additions & 5 deletions chunky/src/java/se/llbit/chunky/world/ChunkPosition.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package se.llbit.chunky.world;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* A chunk position consists of two integer coordinates x and z.
Expand All @@ -35,6 +34,19 @@ private ChunkPosition(int x, int z) {
this.z = z;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChunkPosition that = (ChunkPosition) o;
return x == that.x && z == that.z;
}

@Override
public int hashCode() {
return z * 31 + x;
}

@Override
public String toString() {
return "[" + x + ", " + z + "]";
Expand All @@ -44,11 +56,8 @@ public ChunkPosition regionPosition() {
return get(x >> 5, z >> 5);
}

//not using synchronized Long2ReferenceOpenHashMap due to using one lock. ConcurrentHashMap locks on buckets
private static final Map<Long, ChunkPosition> positions = new ConcurrentHashMap<>();

public static ChunkPosition get(int x, int z) {
return positions.computeIfAbsent(positionToLong(x, z), (position) -> new ChunkPosition(x, z));
return new ChunkPosition(x, z);
}

public static long positionToLong(int x, int z) {
Expand Down
12 changes: 10 additions & 2 deletions chunky/src/java/se/llbit/math/PackedOctree.java
Original file line number Diff line number Diff line change
Expand Up @@ -289,14 +289,19 @@ private boolean nodeEquals(int firstNodeIndex, int secondNodeIndex) {
return (!firstIsBranch && !secondIsBranch && treeData[firstNodeIndex] == treeData[secondNodeIndex]); // compare types
}

private int[] parents = new int[depth];

/**
* Sets a specified block within the octree to a specific palette value, subdividing and merging as needed.
*
* x, y, z are in octree coordinates, NOT world coordinates.
*/
@Override
public void set(int type, int x, int y, int z) {
int[] parents = new int[depth]; // better to put as a field to prevent allocation at each invocation?
if (this.parents.length < depth) {
this.parents = new int[depth];
}
int[] parents = this.parents;
int nodeIndex = 0; // start at root
int position;

Expand Down Expand Up @@ -449,7 +454,10 @@ else if(firstType != tempTree.get(curDepth+1)[childrenIdx+childNo] && tempTree.g

int type = tempTree.get(0)[0];

int[] parents = new int[depth]; // better to put as a field to prevent allocation at each invocation?
if (this.parents.length < depth) {
this.parents = new int[depth];
}
int[] parents = this.parents;
int nodeIndex = 0; // start at root
int position;

Expand Down