diff --git a/overviewer_core/biome.py b/overviewer_core/biome.py
index 4e20f7f..9910165 100644
--- a/overviewer_core/biome.py
+++ b/overviewer_core/biome.py
@@ -14,31 +14,17 @@
# You should have received a copy of the GNU General Public License along
# with the Overviewer. If not, see .
-import numpy
-class BiomeDispensary:
- """Turns biome arrays of either 256 or 1024 integer values into 16x16 2d arrays,
- which can then be retrieved for any Y level with get_biome.
- """
- def __init__(self, biome_array):
- self.biome_len = len(biome_array)
- if self.biome_len == 256:
- self.biomes = [biome_array.reshape((16, 16))]
- elif self.biome_len == 1024:
- self.biomes = [None] * 16
- # Each value in the biome array actually stands for 4 blocks, so we take the
- # Kronecker product of it to "scale" it into its full size of 4096 entries
- krond = numpy.kron(biome_array, numpy.ones((4)))
- for i in range(0, 16):
- # Now we can divide it into 16x16 slices
- self.biomes[i] = krond[i * 256:(i + 1) * 256].reshape((16, 16))
-
-
- def get_biome(self, y_level):
- if self.biome_len == 256 or y_level < 0:
- return self.biomes[0]
- else:
- # We clamp the value to a max of 15 here because apparently Y=16
- # also exists, and while I don't know what biome level Mojang uses for
- # that, the highest one is probably a good bet.
- return self.biomes[min(y_level, 15)]
+def reshape_biome_data(biome_array):
+ biome_len = len(biome_array)
+ if biome_len == 256:
+ return biome_array.reshape((16, 16))
+ elif biome_len == 1024:
+ # Ok here's the big brain explanation:
+ # Minecraft's new biomes have a resolution of 4x4x4 blocks.
+ # This means for a 16x256x16 chunk column we get 64 times for the vertical,
+ # and 4x4 values for the horizontals.
+ # Minecraft Wiki says some dumb thing about how "oh it's ordered by Z, then X, then Y",
+ # but they appear to either be wrong or have explained it with the eloquence of a
+ # caveman.
+ return biome_array.reshape((4, 64, 4))
diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c
index 99a8b3d..f27e400 100644
--- a/overviewer_core/src/iterate.c
+++ b/overviewer_core/src/iterate.c
@@ -109,12 +109,10 @@ static inline void load_chunk_section(ChunkData* dest, int32_t i, PyObject* sect
dest->sections[i].data = (PyArrayObject*)PyDict_GetItemString(section, "Data");
dest->sections[i].skylight = (PyArrayObject*)PyDict_GetItemString(section, "SkyLight");
dest->sections[i].blocklight = (PyArrayObject*)PyDict_GetItemString(section, "BlockLight");
- dest->sections[i].biomes = (PyArrayObject*)PyDict_GetItemString(section, "Biomes");
Py_INCREF(dest->sections[i].blocks);
Py_INCREF(dest->sections[i].data);
Py_INCREF(dest->sections[i].skylight);
Py_INCREF(dest->sections[i].blocklight);
- Py_INCREF(dest->sections[i].biomes);
}
/* loads the given chunk into the chunks[] array in the state
@@ -132,12 +130,13 @@ bool load_chunk(RenderState* state, int32_t x, int32_t z, uint8_t required) {
if (dest->loaded)
return false;
+ /* set reasonable defaults */
+ dest->biomes = NULL;
for (i = 0; i < SECTIONS_PER_CHUNK; i++) {
dest->sections[i].blocks = NULL;
dest->sections[i].data = NULL;
dest->sections[i].skylight = NULL;
dest->sections[i].blocklight = NULL;
- dest->sections[i].biomes = NULL;
}
dest->loaded = 1;
@@ -167,6 +166,9 @@ bool load_chunk(RenderState* state, int32_t x, int32_t z, uint8_t required) {
return true;
}
+ dest->biomes = (PyArrayObject*)PyDict_GetItemString(chunk, "Biomes");
+ Py_INCREF(dest->biomes);
+ dest->new_biomes = PyObject_IsTrue(PyDict_GetItemString(chunk, "NewBiomes"));
for (i = 0; i < PySequence_Fast_GET_SIZE(sections); i++) {
PyObject* ycoord = NULL;
@@ -193,12 +195,12 @@ unload_all_chunks(RenderState* state) {
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
if (state->chunks[i][j].loaded) {
+ Py_XDECREF(state->chunks[i][j].biomes);
for (k = 0; k < SECTIONS_PER_CHUNK; k++) {
Py_XDECREF(state->chunks[i][j].sections[k].blocks);
Py_XDECREF(state->chunks[i][j].sections[k].data);
Py_XDECREF(state->chunks[i][j].sections[k].skylight);
Py_XDECREF(state->chunks[i][j].sections[k].blocklight);
- Py_XDECREF(state->chunks[i][j].sections[k].biomes);
}
state->chunks[i][j].loaded = 0;
}
diff --git a/overviewer_core/src/overviewer.h b/overviewer_core/src/overviewer.h
index 8cac99d..a19688e 100644
--- a/overviewer_core/src/overviewer.h
+++ b/overviewer_core/src/overviewer.h
@@ -29,9 +29,9 @@
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
-// increment this value if you've made a change to the c extesion
+// increment this value if you've made a change to the c extension
// and want to force users to rebuild
-#define OVERVIEWER_EXTENSION_VERSION 78
+#define OVERVIEWER_EXTENSION_VERSION 79
#include
#include
@@ -84,10 +84,12 @@ typedef struct {
int32_t loaded;
/* chunk biome array */
PyArrayObject* biomes;
+ /* whether this is a 3d biome array */
+ bool new_biomes;
/* all the sections in a given chunk */
struct {
/* all there is to know about each section */
- PyArrayObject *blocks, *data, *skylight, *blocklight, *biomes;
+ PyArrayObject *blocks, *data, *skylight, *blocklight;
} sections[SECTIONS_PER_CHUNK];
} ChunkData;
typedef struct {
@@ -210,7 +212,7 @@ static inline uint32_t get_data(RenderState* state, DataType type, int32_t x, in
data_array = state->chunks[chunkx][chunkz].sections[chunky].skylight;
break;
case BIOMES:
- data_array = state->chunks[chunkx][chunkz].sections[chunky].biomes;
+ data_array = state->chunks[chunkx][chunkz].biomes;
};
if (data_array == NULL)
@@ -218,8 +220,13 @@ static inline uint32_t get_data(RenderState* state, DataType type, int32_t x, in
if (type == BLOCKS)
return getArrayShort3D(data_array, x, y, z);
- if (type == BIOMES)
- return getArrayByte2D(data_array, x, z);
+ if (type == BIOMES) {
+ if (state->chunks[chunkx][chunkz].new_biomes) {
+ return getArrayByte3D(data_array, x / 4, y / 4, z / 4);
+ } else {
+ return getArrayByte2D(data_array, x, z);
+ }
+ }
return getArrayByte3D(data_array, x, y, z);
}
diff --git a/overviewer_core/world.py b/overviewer_core/world.py
index 2768843..691c1df 100644
--- a/overviewer_core/world.py
+++ b/overviewer_core/world.py
@@ -26,7 +26,7 @@ import numpy
from . import nbt
from . import cache
-from .biome import BiomeDispensary
+from .biome import reshape_biome_data
"""
This module has routines for extracting information about available worlds
@@ -1372,15 +1372,14 @@ class RegionSet(object):
biomes = numpy.frombuffer(biomes, dtype=numpy.uint8)
else:
biomes = numpy.asarray(biomes)
- #biomes = biomes.reshape((16,16))
- biome_giver = BiomeDispensary(biomes)
+ biomes = reshape_biome_data(biomes)
else:
# Worlds converted by Jeb's program may be missing the Biomes key.
# Additionally, 19w09a worlds have an empty array as biomes key
# in some cases.
- #biomes = numpy.zeros((16, 16), dtype=numpy.uint8)
- biome_giver = BiomeDispensary(numpy.zeros(256, dtype=numpy.uint8))
- #chunk_data['Biomes'] = biomes
+ biomes = numpy.zeros((16, 16), dtype=numpy.uint8)
+ chunk_data['Biomes'] = biomes
+ chunk_data['NewBiomes'] = (len(biomes.shape) == 3)
unrecognized_block_types = {}
for section in chunk_data['Sections']:
@@ -1388,7 +1387,6 @@ class RegionSet(object):
# Turn the skylight array into a 16x16x16 matrix. The array comes
# packed 2 elements per byte, so we need to expand it.
try:
- section['Biomes'] = biome_giver.get_biome(section["Y"])
if 'SkyLight' in section:
skylight = numpy.frombuffer(section['SkyLight'], dtype=numpy.uint8)
skylight = skylight.reshape((16,16,8))
@@ -1612,9 +1610,6 @@ class RotatedRegionSet(RegionSetWrapper):
for section in chunk_data['Sections']:
section = dict(section)
newsections.append(section)
- biomes = numpy.swapaxes(section['Biomes'], 0, 1)
- biomes = numpy.rot90(biomes, self.north_dir)
- section['Biomes'] = numpy.swapaxes(biomes, 0, 1)
for arrayname in ['Blocks', 'Data', 'SkyLight', 'BlockLight']:
array = section[arrayname]
# Since the anvil change, arrays are arranged with axes Y,Z,X
@@ -1626,7 +1621,15 @@ class RotatedRegionSet(RegionSetWrapper):
section[arrayname] = array
chunk_data['Sections'] = newsections
- # same as above, for biomes (Z/X indexed)
+ if chunk_data['NewBiomes']:
+ array = numpy.swapaxes(chunk_data['Biomes'], 0, 2)
+ array = numpy.rot90(array, self.north_dir)
+ chunk_data['Biomes'] = numpy.swapaxes(array, 0, 2)
+ else:
+ # same as above, for biomes (Z/X indexed)
+ biomes = numpy.swapaxes(chunk_data['Biomes'], 0, 1)
+ biomes = numpy.rot90(biomes, self.north_dir)
+ chunk_data['Biomes'] = numpy.swapaxes(biomes, 0, 1)
return chunk_data
def get_chunk_mtime(self, x, z):