Completely change biome code again
Turns out my previous grasp of how Minecraft does this was wrong. This seems to be the correct way. One side effect is that biome data now has less resolution. One only really notices this when looking at water, for which Minecraft does not even use the water colours for in-game, otherwise I can't really tell a big difference. Fixes #1698.
This commit is contained in:
@@ -14,31 +14,17 @@
|
|||||||
# You should have received a copy of the GNU General Public License along
|
# You should have received a copy of the GNU General Public License along
|
||||||
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
|
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
class BiomeDispensary:
|
def reshape_biome_data(biome_array):
|
||||||
"""Turns biome arrays of either 256 or 1024 integer values into 16x16 2d arrays,
|
biome_len = len(biome_array)
|
||||||
which can then be retrieved for any Y level with get_biome.
|
if biome_len == 256:
|
||||||
"""
|
return biome_array.reshape((16, 16))
|
||||||
def __init__(self, biome_array):
|
elif biome_len == 1024:
|
||||||
self.biome_len = len(biome_array)
|
# Ok here's the big brain explanation:
|
||||||
if self.biome_len == 256:
|
# Minecraft's new biomes have a resolution of 4x4x4 blocks.
|
||||||
self.biomes = [biome_array.reshape((16, 16))]
|
# This means for a 16x256x16 chunk column we get 64 times for the vertical,
|
||||||
elif self.biome_len == 1024:
|
# and 4x4 values for the horizontals.
|
||||||
self.biomes = [None] * 16
|
# Minecraft Wiki says some dumb thing about how "oh it's ordered by Z, then X, then Y",
|
||||||
# Each value in the biome array actually stands for 4 blocks, so we take the
|
# but they appear to either be wrong or have explained it with the eloquence of a
|
||||||
# Kronecker product of it to "scale" it into its full size of 4096 entries
|
# caveman.
|
||||||
krond = numpy.kron(biome_array, numpy.ones((4)))
|
return biome_array.reshape((4, 64, 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)]
|
|
||||||
|
|||||||
@@ -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].data = (PyArrayObject*)PyDict_GetItemString(section, "Data");
|
||||||
dest->sections[i].skylight = (PyArrayObject*)PyDict_GetItemString(section, "SkyLight");
|
dest->sections[i].skylight = (PyArrayObject*)PyDict_GetItemString(section, "SkyLight");
|
||||||
dest->sections[i].blocklight = (PyArrayObject*)PyDict_GetItemString(section, "BlockLight");
|
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].blocks);
|
||||||
Py_INCREF(dest->sections[i].data);
|
Py_INCREF(dest->sections[i].data);
|
||||||
Py_INCREF(dest->sections[i].skylight);
|
Py_INCREF(dest->sections[i].skylight);
|
||||||
Py_INCREF(dest->sections[i].blocklight);
|
Py_INCREF(dest->sections[i].blocklight);
|
||||||
Py_INCREF(dest->sections[i].biomes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* loads the given chunk into the chunks[] array in the state
|
/* 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)
|
if (dest->loaded)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
/* set reasonable defaults */
|
||||||
|
dest->biomes = NULL;
|
||||||
for (i = 0; i < SECTIONS_PER_CHUNK; i++) {
|
for (i = 0; i < SECTIONS_PER_CHUNK; i++) {
|
||||||
dest->sections[i].blocks = NULL;
|
dest->sections[i].blocks = NULL;
|
||||||
dest->sections[i].data = NULL;
|
dest->sections[i].data = NULL;
|
||||||
dest->sections[i].skylight = NULL;
|
dest->sections[i].skylight = NULL;
|
||||||
dest->sections[i].blocklight = NULL;
|
dest->sections[i].blocklight = NULL;
|
||||||
dest->sections[i].biomes = NULL;
|
|
||||||
}
|
}
|
||||||
dest->loaded = 1;
|
dest->loaded = 1;
|
||||||
|
|
||||||
@@ -167,6 +166,9 @@ bool load_chunk(RenderState* state, int32_t x, int32_t z, uint8_t required) {
|
|||||||
return true;
|
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++) {
|
for (i = 0; i < PySequence_Fast_GET_SIZE(sections); i++) {
|
||||||
PyObject* ycoord = NULL;
|
PyObject* ycoord = NULL;
|
||||||
@@ -193,12 +195,12 @@ unload_all_chunks(RenderState* state) {
|
|||||||
for (i = 0; i < 3; i++) {
|
for (i = 0; i < 3; i++) {
|
||||||
for (j = 0; j < 3; j++) {
|
for (j = 0; j < 3; j++) {
|
||||||
if (state->chunks[i][j].loaded) {
|
if (state->chunks[i][j].loaded) {
|
||||||
|
Py_XDECREF(state->chunks[i][j].biomes);
|
||||||
for (k = 0; k < SECTIONS_PER_CHUNK; k++) {
|
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].blocks);
|
||||||
Py_XDECREF(state->chunks[i][j].sections[k].data);
|
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].skylight);
|
||||||
Py_XDECREF(state->chunks[i][j].sections[k].blocklight);
|
Py_XDECREF(state->chunks[i][j].sections[k].blocklight);
|
||||||
Py_XDECREF(state->chunks[i][j].sections[k].biomes);
|
|
||||||
}
|
}
|
||||||
state->chunks[i][j].loaded = 0;
|
state->chunks[i][j].loaded = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
|
|
||||||
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
#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
|
// and want to force users to rebuild
|
||||||
#define OVERVIEWER_EXTENSION_VERSION 78
|
#define OVERVIEWER_EXTENSION_VERSION 79
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -84,10 +84,12 @@ typedef struct {
|
|||||||
int32_t loaded;
|
int32_t loaded;
|
||||||
/* chunk biome array */
|
/* chunk biome array */
|
||||||
PyArrayObject* biomes;
|
PyArrayObject* biomes;
|
||||||
|
/* whether this is a 3d biome array */
|
||||||
|
bool new_biomes;
|
||||||
/* all the sections in a given chunk */
|
/* all the sections in a given chunk */
|
||||||
struct {
|
struct {
|
||||||
/* all there is to know about each section */
|
/* all there is to know about each section */
|
||||||
PyArrayObject *blocks, *data, *skylight, *blocklight, *biomes;
|
PyArrayObject *blocks, *data, *skylight, *blocklight;
|
||||||
} sections[SECTIONS_PER_CHUNK];
|
} sections[SECTIONS_PER_CHUNK];
|
||||||
} ChunkData;
|
} ChunkData;
|
||||||
typedef struct {
|
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;
|
data_array = state->chunks[chunkx][chunkz].sections[chunky].skylight;
|
||||||
break;
|
break;
|
||||||
case BIOMES:
|
case BIOMES:
|
||||||
data_array = state->chunks[chunkx][chunkz].sections[chunky].biomes;
|
data_array = state->chunks[chunkx][chunkz].biomes;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data_array == NULL)
|
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)
|
if (type == BLOCKS)
|
||||||
return getArrayShort3D(data_array, x, y, z);
|
return getArrayShort3D(data_array, x, y, z);
|
||||||
if (type == BIOMES)
|
if (type == BIOMES) {
|
||||||
return getArrayByte2D(data_array, x, z);
|
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);
|
return getArrayByte3D(data_array, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import numpy
|
|||||||
|
|
||||||
from . import nbt
|
from . import nbt
|
||||||
from . import cache
|
from . import cache
|
||||||
from .biome import BiomeDispensary
|
from .biome import reshape_biome_data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module has routines for extracting information about available worlds
|
This module has routines for extracting information about available worlds
|
||||||
@@ -1372,15 +1372,14 @@ class RegionSet(object):
|
|||||||
biomes = numpy.frombuffer(biomes, dtype=numpy.uint8)
|
biomes = numpy.frombuffer(biomes, dtype=numpy.uint8)
|
||||||
else:
|
else:
|
||||||
biomes = numpy.asarray(biomes)
|
biomes = numpy.asarray(biomes)
|
||||||
#biomes = biomes.reshape((16,16))
|
biomes = reshape_biome_data(biomes)
|
||||||
biome_giver = BiomeDispensary(biomes)
|
|
||||||
else:
|
else:
|
||||||
# Worlds converted by Jeb's program may be missing the Biomes key.
|
# Worlds converted by Jeb's program may be missing the Biomes key.
|
||||||
# Additionally, 19w09a worlds have an empty array as biomes key
|
# Additionally, 19w09a worlds have an empty array as biomes key
|
||||||
# in some cases.
|
# in some cases.
|
||||||
#biomes = numpy.zeros((16, 16), dtype=numpy.uint8)
|
biomes = numpy.zeros((16, 16), dtype=numpy.uint8)
|
||||||
biome_giver = BiomeDispensary(numpy.zeros(256, dtype=numpy.uint8))
|
chunk_data['Biomes'] = biomes
|
||||||
#chunk_data['Biomes'] = biomes
|
chunk_data['NewBiomes'] = (len(biomes.shape) == 3)
|
||||||
|
|
||||||
unrecognized_block_types = {}
|
unrecognized_block_types = {}
|
||||||
for section in chunk_data['Sections']:
|
for section in chunk_data['Sections']:
|
||||||
@@ -1388,7 +1387,6 @@ class RegionSet(object):
|
|||||||
# Turn the skylight array into a 16x16x16 matrix. The array comes
|
# Turn the skylight array into a 16x16x16 matrix. The array comes
|
||||||
# packed 2 elements per byte, so we need to expand it.
|
# packed 2 elements per byte, so we need to expand it.
|
||||||
try:
|
try:
|
||||||
section['Biomes'] = biome_giver.get_biome(section["Y"])
|
|
||||||
if 'SkyLight' in section:
|
if 'SkyLight' in section:
|
||||||
skylight = numpy.frombuffer(section['SkyLight'], dtype=numpy.uint8)
|
skylight = numpy.frombuffer(section['SkyLight'], dtype=numpy.uint8)
|
||||||
skylight = skylight.reshape((16,16,8))
|
skylight = skylight.reshape((16,16,8))
|
||||||
@@ -1612,9 +1610,6 @@ class RotatedRegionSet(RegionSetWrapper):
|
|||||||
for section in chunk_data['Sections']:
|
for section in chunk_data['Sections']:
|
||||||
section = dict(section)
|
section = dict(section)
|
||||||
newsections.append(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']:
|
for arrayname in ['Blocks', 'Data', 'SkyLight', 'BlockLight']:
|
||||||
array = section[arrayname]
|
array = section[arrayname]
|
||||||
# Since the anvil change, arrays are arranged with axes Y,Z,X
|
# Since the anvil change, arrays are arranged with axes Y,Z,X
|
||||||
@@ -1626,7 +1621,15 @@ class RotatedRegionSet(RegionSetWrapper):
|
|||||||
section[arrayname] = array
|
section[arrayname] = array
|
||||||
chunk_data['Sections'] = newsections
|
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
|
return chunk_data
|
||||||
|
|
||||||
def get_chunk_mtime(self, x, z):
|
def get_chunk_mtime(self, x, z):
|
||||||
|
|||||||
Reference in New Issue
Block a user