Skip to content

Commit

Permalink
Add 3d biomes and 3d blur (off by default) (#1225)
Browse files Browse the repository at this point in the history
* Add trivial implementation of 3d biomes and 3d blur (off by default)

In the future it would be great to add a better implementation for performance
 - this is good enough for now

* Rename using3dBiomes->use3dBiomes

* Fixes and refactoring to trivial implementation datastructures

* Complete implementing BiomeStructure
BiomeStructure now completely replaces WorldTexture, which are now loaded as BiomeStructures if the octree format is outdated
Add biomeBlending toggle
Add reload chunks confirmation on relevant changes to biome settings (with minor jank due to no snapshotting)
Refactoring of the BiomeStructure implementations

* Add additional javadoc describing how the trivial BiomeStructure implementations are stored for futures reference

* Remove incorrectly boxed int due to changed implementation

* Add better error handling for BiomeStructure registry usage and loading itself

* Add selection option for BiomeStructure in the UI

Return a default implementation instead of null for an invalid BiomeStructure registry key
similar to how the octree factory works

* Implement better hashing for xyz positions

* Add BiomeStructure.Factory.createIndexStructure, for biome palette indices implementation
It defaults to the previous implementation

* Simplify initial checkbox disable logic
  • Loading branch information
NotStirred committed May 25, 2022
1 parent c54b1e0 commit 785b362
Show file tree
Hide file tree
Showing 19 changed files with 932 additions and 108 deletions.
4 changes: 4 additions & 0 deletions chunky/src/java/se/llbit/chunky/main/Chunky.java
Expand Up @@ -34,6 +34,7 @@
import se.llbit.chunky.renderer.scene.SceneFactory;
import se.llbit.chunky.renderer.scene.SceneManager;
import se.llbit.chunky.renderer.scene.SynchronousSceneManager;
import se.llbit.chunky.renderer.scene.biome.BiomeStructure;
import se.llbit.chunky.resources.ResourcePackLoader;
import se.llbit.chunky.resources.SettingsDirectory;
import se.llbit.chunky.resources.TexturePackLoader;
Expand Down Expand Up @@ -63,6 +64,9 @@
* <p>Read more about Chunky at <a href="https://chunky.llbit.se">https://chunky.llbit.se</a>.
*/
public class Chunky {
static {
BiomeStructure.registerDefaults();
}

/**
* A log receiver suitable for headless rendering.
Expand Down
Expand Up @@ -115,7 +115,7 @@ private static boolean waterPlaneIntersection(Scene scene, Ray ray) {
if (scene.getWaterPlaneChunkClip()) {
Vector3 pos = new Vector3(ray.o);
pos.scaleAdd(t, ray.d);
if (scene.isChunkLoaded((int)Math.floor(pos.x), (int)Math.floor(pos.z)))
if (scene.isChunkLoaded((int)Math.floor(pos.x), (int)Math.floor(pos.y), (int)Math.floor(pos.z)))
return false;
}
if (ray.d.y < 0) {
Expand Down
300 changes: 219 additions & 81 deletions chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java

Large diffs are not rendered by default.

@@ -0,0 +1,226 @@
package se.llbit.chunky.renderer.scene.biome;

import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import se.llbit.chunky.world.Chunk;
import se.llbit.chunky.world.WorldTexture;
import se.llbit.log.Log;
import se.llbit.math.structures.Position2IntStructure;
import se.llbit.math.structures.Position2ReferenceStructure;
import se.llbit.math.structures.Position2d2IntPackedArray;
import se.llbit.math.structures.Position3d2IntPackedArray;
import se.llbit.util.annotation.NotNull;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.BitSet;
import java.util.Map;

public interface BiomeStructure extends Position2ReferenceStructure<float[]> {
Map<String, Factory> REGISTRY = new Object2ReferenceOpenHashMap<>();
String DEFAULT_IMPLEMENTATION = "TRIVIAL_2D";

static void registerDefaults() {
//TODO: create a plugin api interface for registering implementations, and move this to that
BiomeStructure.register("TRIVIAL_3D", new Factory() {
@Override
public BiomeStructure create() {
return new Trivial3dBiomeStructureImpl();
}

@Override
public BiomeStructure load(@NotNull DataInputStream in) throws IOException {
/*
* Stored as:
* (int) size
* (int) x, y, z
* (long) Length of present bitset in longs
* (BitSet as longs) Present values bitset
* (int) number of values stored
* (float[][]) The internal data of each packed x,y,z position
*/

Trivial3dBiomeStructureImpl impl = new Trivial3dBiomeStructureImpl();
int size = in.readInt();
for (int i = 0; i < size; i++) {
int x = in.readInt();
int y = in.readInt();
int z = in.readInt();

long[] longs = new long[in.readInt()];
for (int bitsetIdx = 0; bitsetIdx < longs.length; bitsetIdx++) {
longs[bitsetIdx] = in.readLong();
}

BitSet presentValues = BitSet.valueOf(longs);

int count = in.readInt();
float[][] floats = new float[count][];
for (int idx = 0; idx < count; idx++) {
if(presentValues.get(idx)) {
float[] farray = new float[3];
farray[0] = in.readFloat();
farray[1] = in.readFloat();
farray[2] = in.readFloat();
floats[idx] = farray;
}
}
impl.setCube(x, y, z, floats);
}
return impl;
}

@Override
public boolean is3d() {
return true;
}
});

BiomeStructure.register("TRIVIAL_2D", new Factory() {
@Override
public BiomeStructure create() {
return new Trivial2dBiomeStructureImpl();
}

/**
* Stored as:
* (int) size
* (int) long packed key
* (long) Length of present bitset in longs
* (BitSet as longs) Present values bitset
* (int) number of values stored
* (float[][]) The internal data of each packed x,z position
*/
@Override
public BiomeStructure load(@NotNull DataInputStream in) throws IOException {
Trivial2dBiomeStructureImpl impl = new Trivial2dBiomeStructureImpl();
int size = in.readInt();
for (int i = 0; i < size; i++) {
long key = in.readLong();

long[] longs = new long[in.readInt()];
for (int bitsetIdx = 0; bitsetIdx < longs.length; bitsetIdx++) {
longs[bitsetIdx] = in.readLong();
}

BitSet presentValues = BitSet.valueOf(longs);

int count = in.readInt();
float[][] floats = new float[count][];
for (int idx = 0; idx < count; idx++) {
if(presentValues.get(idx)) {
float[] farray = new float[3];
farray[0] = in.readFloat();
farray[1] = in.readFloat();
farray[2] = in.readFloat();
floats[idx] = farray;
}
}
impl.setCube(key, floats);
}
return impl;
}

@Override
public boolean is3d() {
return false;
}
});
}

/**
* This is basically a reimplementation of {@link WorldTexture#load} but instead loading into an arbitrary
* BiomeStructure implementation
*
* @param impl The implementation to load the legacy implementation into
* @param in The serialised legacy data in an input stream
* @return The newly constructed {@link BiomeStructure} of the specified implementation
*/
static BiomeStructure loadLegacy(Factory impl, DataInputStream in) throws IOException {
BiomeStructure biomeStructure = impl.create();
int numTiles = in.readInt();
for (int i = 0; i < numTiles; ++i) {
int chunkX = in.readInt();
int chunkZ = in.readInt();
for (int localX = 0; localX < Chunk.X_MAX; localX++) {
for (int localZ = 0; localZ < Chunk.Z_MAX; localZ++) {
biomeStructure.set(chunkX * Chunk.X_MAX + localX, 0, chunkZ * Chunk.Z_MAX + localZ, new float[] {
in.readFloat(),
in.readFloat(),
in.readFloat()
});
}
}
}
biomeStructure.compact();
return biomeStructure;
}

/**
* Register a new {@link Factory}
* @param key The key to register the factory under <b>(MUST BE UNIQUE)</b>
* @return Whether the supplied factory overwrote an existing one
*/
static boolean register(@NotNull String key, @NotNull Factory factory) {
return REGISTRY.put(key, factory) != null;
}

/**
* @return Get the specified implementation from the registry, if it doesn't exist, the default implementation is
* returned
*/
@NotNull
static Factory get(@NotNull String key) throws NullPointerException {
Factory factory = REGISTRY.get(key);
if(factory == null) {
Log.warnf("Implementation %s does not exist, using the default: %s", key, DEFAULT_IMPLEMENTATION);
return REGISTRY.get(DEFAULT_IMPLEMENTATION);
}
return factory;
}

/**
* Store the {@link BiomeStructure} to a data output stream
*/
void store(DataOutputStream out) throws IOException;

/**
* This method is called to tell the implementation to shrink its size. (Node-tree optimisation, etc.)
* Called when throughout insertion of new biomes, and on completion
*/
void compact();

/**
* @return The registry key this biome format uses. Must be unique
*/
String biomeFormat();

interface Factory {
/**
* Create an empty {@link BiomeStructure} for loading a new scene
*/
BiomeStructure create();

/**
* Create an empty {@link Position2IntStructure} for the biome palette indices
* (used only for biome blending, does not need to be saved)
*/
default Position2IntStructure createIndexStructure() {
if(is3d()) {
return new Position3d2IntPackedArray();
} else {
return new Position2d2IntPackedArray();
}
}

/**
* Load a saved {@link BiomeStructure} of this implementation from a {@link DataInputStream}
*/
BiomeStructure load(@NotNull DataInputStream in) throws IOException;

/**
* @return Whether the implementation created is 3d
*/
boolean is3d();
}
}
@@ -0,0 +1,53 @@
package se.llbit.chunky.renderer.scene.biome;

import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import se.llbit.math.structures.Position2d2ReferencePackedArrayStructure;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.BitSet;

public class Trivial2dBiomeStructureImpl extends Position2d2ReferencePackedArrayStructure<float[]> implements BiomeStructure {

public void setCube(long packedPosition, float[][] data) {
this.map.put(packedPosition, data);
}

@Override
public void store(DataOutputStream out) throws IOException {
out.writeInt(this.map.size());
for (Long2ReferenceMap.Entry<float[][]> entry : this.map.long2ReferenceEntrySet()) {
out.writeLong(entry.getLongKey());
Object[] value = entry.getValue();

BitSet presentValues = new BitSet(value.length);
for (int i = 0, valueLength = value.length; i < valueLength; i++) {
presentValues.set(i, value[i] != null);
}
long[] longs = presentValues.toLongArray();
out.writeInt(longs.length);
for (long l : longs) {
out.writeLong(l);
}

out.writeInt(value.length);
for (Object o : value) {
if (o != null) {
for (float f : (float[]) o) {
out.writeFloat(f);
}
}
}
}
}

@Override
public String biomeFormat() {
return "TRIVIAL_2D";
}

@Override
public void compact() {

}
}
@@ -0,0 +1,56 @@
package se.llbit.chunky.renderer.scene.biome;

import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
import se.llbit.math.structures.Position3d2ReferencePackedArrayStructure;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.BitSet;

public class Trivial3dBiomeStructureImpl extends Position3d2ReferencePackedArrayStructure<float[]> implements BiomeStructure {

public void setCube(int x, int y, int z, float[][] data) {
this.map.put(new XYZTriple(x, y, z), data);
}

@Override
public void store(DataOutputStream out) throws IOException {
out.writeInt(this.map.size());
for (Object2ReferenceMap.Entry<XYZTriple, float[][]> entry : this.map.object2ReferenceEntrySet()) {
XYZTriple key = entry.getKey();
out.writeInt(key.x);
out.writeInt(key.y);
out.writeLong(key.z);
Object[] value = entry.getValue();

BitSet presentValues = new BitSet(value.length);
for (int i = 0, valueLength = value.length; i < valueLength; i++) {
presentValues.set(i, value[i] != null);
}
long[] longs = presentValues.toLongArray();
out.writeInt(longs.length);
for (long l : longs) {
out.writeLong(l);
}

out.writeInt(value.length);
for (Object o : value) {
if (o != null) {
for (float f : (float[]) o) {
out.writeFloat(f);
}
}
}
}
}

@Override
public String biomeFormat() {
return "TRIVIAL_3D";
}

@Override
public void compact() {

}
}

0 comments on commit 785b362

Please sign in to comment.