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):