0

biomes now work, still no swamp purple though

This commit is contained in:
Aaron Griffith
2012-02-22 04:14:44 -05:00
parent 4ac7c1222a
commit 414b754e24
4 changed files with 159 additions and 176 deletions

View File

@@ -146,9 +146,9 @@ int load_chunk(RenderState* state, int x, int z, unsigned char required) {
if (sections) { if (sections) {
sections = PySequence_Fast(sections, "Sections tag was not a list!"); sections = PySequence_Fast(sections, "Sections tag was not a list!");
} }
Py_DECREF(chunk);
if (sections == NULL) { if (sections == NULL) {
// exception set, again // exception set, again
Py_DECREF(chunk);
if (!required) { if (!required) {
PyErr_Clear(); PyErr_Clear();
} }
@@ -156,6 +156,7 @@ int load_chunk(RenderState* state, int x, int z, unsigned char required) {
} }
/* set up reasonable defaults */ /* set up 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;
@@ -164,6 +165,9 @@ int load_chunk(RenderState* state, int x, int z, unsigned char required) {
dest->sections[i].blocklight = NULL; dest->sections[i].blocklight = NULL;
} }
dest->biomes = PyDict_GetItemString(chunk, "Biomes");
Py_INCREF(dest->biomes);
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;
int sectiony = 0; int sectiony = 0;
@@ -177,6 +181,7 @@ int load_chunk(RenderState* state, int x, int z, unsigned char required) {
load_chunk_section(dest, sectiony, section); load_chunk_section(dest, sectiony, section);
} }
Py_DECREF(sections); Py_DECREF(sections);
Py_DECREF(chunk);
dest->loaded = 1; dest->loaded = 1;
return 0; return 0;
@@ -573,6 +578,7 @@ chunk_render(PyObject *self, PyObject *args) {
for (j = 0; j < 3; j++) { for (j = 0; j < 3; j++) {
if (state.chunks[i][j].loaded) { if (state.chunks[i][j].loaded) {
int k; int k;
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);

View File

@@ -26,7 +26,7 @@
// 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 extesion
// and want to force users to rebuild // and want to force users to rebuild
#define OVERVIEWER_EXTENSION_VERSION 24 #define OVERVIEWER_EXTENSION_VERSION 25
/* Python PIL, and numpy headers */ /* Python PIL, and numpy headers */
#include <Python.h> #include <Python.h>
@@ -38,11 +38,13 @@
in y/z/x order */ in y/z/x order */
#define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (y), (z), (x)))) #define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (y), (z), (x))))
#define getArrayShort3D(array, x,y,z) (*(unsigned short *)(PyArray_GETPTR3((array), (y), (z), (x)))) #define getArrayShort3D(array, x,y,z) (*(unsigned short *)(PyArray_GETPTR3((array), (y), (z), (x))))
#define getArrayByte2D(array, x,y) (*(unsigned char *)(PyArray_GETPTR2((array), (x), (y))))
#define getArrayShort2D(array, x,y) (*(unsigned short *)(PyArray_GETPTR2((array), (x), (y)))) #define getArrayShort2D(array, x,y) (*(unsigned short *)(PyArray_GETPTR2((array), (x), (y))))
/* generally useful MAX / MIN macros */ /* generally useful MAX / MIN macros */
#define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b))
#define CLAMP(x, a, b) (MIN(MAX(x, a), b))
/* in composite.c */ /* in composite.c */
Imaging imaging_python_to_c(PyObject *obj); Imaging imaging_python_to_c(PyObject *obj);
@@ -71,6 +73,8 @@ typedef struct _RenderMode RenderMode;
typedef struct { typedef struct {
/* whether this chunk is loaded: use load_chunk to load */ /* whether this chunk is loaded: use load_chunk to load */
int loaded; int loaded;
/* chunk biome array */
PyObject *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 */
@@ -143,6 +147,7 @@ typedef enum
DATA, DATA,
BLOCKLIGHT, BLOCKLIGHT,
SKYLIGHT, SKYLIGHT,
BIOMES,
} DataType; } DataType;
static inline unsigned int get_data(RenderState *state, DataType type, int x, int y, int z) static inline unsigned int get_data(RenderState *state, DataType type, int x, int y, int z)
{ {
@@ -198,6 +203,8 @@ static inline unsigned int get_data(RenderState *state, DataType type, int x, in
case SKYLIGHT: case SKYLIGHT:
data_array = state->chunks[chunkx][chunkz].sections[chunky].skylight; data_array = state->chunks[chunkx][chunkz].sections[chunky].skylight;
break; break;
case BIOMES:
data_array = state->chunks[chunkx][chunkz].biomes;
}; };
if (data_array == NULL) if (data_array == NULL)
@@ -205,6 +212,8 @@ static inline unsigned int get_data(RenderState *state, DataType type, int 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)
return getArrayByte2D(data_array, x, z);
return getArrayByte3D(data_array, x, y, z); return getArrayByte3D(data_array, x, y, z);
} }

View File

@@ -18,30 +18,68 @@
#include "../overviewer.h" #include "../overviewer.h"
typedef struct { typedef struct {
/* biome data for the chunk */
PyObject *biome_data;
/* grasscolor and foliagecolor lookup tables */ /* grasscolor and foliagecolor lookup tables */
PyObject *grasscolor, *foliagecolor, *watercolor; PyObject *grasscolor, *foliagecolor, *watercolor;
/* biome-compatible grass/leaf textures */ /* biome-compatible grass/leaf textures */
PyObject *grass_texture; PyObject *grass_texture;
} PrimitiveBase; } PrimitiveBase;
typedef struct {
const char* name;
float temperature;
float rainfall;
} Biome;
/* each entry in this table is yanked *directly* out of the minecraft source
* temp/rainfall are taken from what MCP calls setTemperatureRainfall
*
* keep in mind the x/y coordinate in the color tables is found *after*
* multiplying rainfall and temperature for the second coordinate, *and* the
* origin is in the lower-right. <3 biomes.
*/
static Biome biome_table[] = {
/* 0 */
{"Ocean", 0.5, 0.5},
{"Plains", 0.8, 0.4},
{"Desert", 2.0, 0.0},
{"Extreme Hills", 0.2, 0.3},
{"Forest", 0.7, 0.8},
/* 5 */
{"Taiga", 0.05, 0.8},
{"Swampland", 0.8, 0.9},
{"River", 0.5, 0.5},
{"Hell", 2.0, 0.0},
{"Sky", 0.5, 0.5},
/* 10 */
{"FrozenOcean", 0.0, 0.5},
{"FrozenRiver", 0.0, 0.5},
{"Ice Plains", 0.0, 0.5},
{"Ice Mountains", 0.0, 0.5},
{"MushroomIsland", 0.9, 1.0},
/* 15 */
{"MushroomIslandShore", 0.9, 1.0},
{"Beach", 0.8, 0.4},
{"DesertHills", 2.0, 0.0},
{"ForestHills", 0.7, 0.8},
{"TaigaHills", 0.05, 0.8},
/* 20 */
{"Extreme Hills Edge", 0.2, 0.3},
{"Jungle", 2.0, 0.45}, /* <-- GUESS, but a good one */
};
#define NUM_BIOMES (sizeof(biome_table) / sizeof(Biome))
static int static int
base_start(void *data, RenderState *state, PyObject *support) { base_start(void *data, RenderState *state, PyObject *support) {
PrimitiveBase *self = (PrimitiveBase *)data; PrimitiveBase *self = (PrimitiveBase *)data;
/* biome-compliant grass mask (includes sides!) */ /* biome-compliant grass mask (includes sides!) */
self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture"); self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture");
self->biome_data = PyObject_CallMethod(state->regionset, "get_biome_data", "ii", state->chunkx, state->chunkz); /* color lookup tables */
if (self->biome_data == NULL) { self->foliagecolor = PyObject_CallMethod(state->textures, "load_foliage_color", "");
/* error while loading biome info, or no biomes at all */ self->grasscolor = PyObject_CallMethod(state->textures, "load_grass_color", "");
PyErr_Clear(); self->watercolor = PyObject_CallMethod(state->textures, "load_water_color", "");
} else {
self->foliagecolor = PyObject_CallMethod(state->textures, "load_foliage_color", "");
self->grasscolor = PyObject_CallMethod(state->textures, "load_grass_color", "");
self->watercolor = PyObject_CallMethod(state->textures, "load_water_color", "");
}
return 0; return 0;
} }
@@ -50,11 +88,10 @@ static void
base_finish(void *data, RenderState *state) { base_finish(void *data, RenderState *state) {
PrimitiveBase *self = (PrimitiveBase *)data; PrimitiveBase *self = (PrimitiveBase *)data;
Py_XDECREF(self->biome_data); Py_DECREF(self->foliagecolor);
Py_XDECREF(self->foliagecolor); Py_DECREF(self->grasscolor);
Py_XDECREF(self->grasscolor); Py_DECREF(self->watercolor);
Py_XDECREF(self->watercolor); Py_DECREF(self->grass_texture);
Py_XDECREF(self->grass_texture);
} }
static int static int
@@ -72,13 +109,6 @@ base_occluded(void *data, RenderState *state, int x, int y, int z) {
return 0; return 0;
} }
static int
base_hidden(void *data, RenderState *state, int x, int y, int z) {
PrimitiveBase *self = (PrimitiveBase *)data;
return 0;
}
static void static void
base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
PrimitiveBase *self = (PrimitiveBase *)data; PrimitiveBase *self = (PrimitiveBase *)data;
@@ -91,9 +121,8 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
* NOTES for maintainers: * NOTES for maintainers:
* *
* To add a biome-compatible block, add an OR'd condition to this * To add a biome-compatible block, add an OR'd condition to this
* following if block, a case to the first switch statement to handle when * following if block, and a case to the switch statement to handle biome
* biome info IS available, and another case to the second switch * coloring.
* statement for when biome info ISN'T available.
* *
* Make sure that in textures.py, the generated textures are the * Make sure that in textures.py, the generated textures are the
* biome-compliant ones! The tinting is now all done here. * biome-compliant ones! The tinting is now all done here.
@@ -116,113 +145,102 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
{ {
/* do the biome stuff! */ /* do the biome stuff! */
PyObject *facemask = mask; PyObject *facemask = mask;
unsigned char r, g, b; unsigned char r = 255, g = 255, b = 255;
PyObject *color_table = NULL;
unsigned char flip_xy = 0;
if (state->block == 2) { if (state->block == 2) {
/* grass needs a special facemask */ /* grass needs a special facemask */
facemask = self->grass_texture; facemask = self->grass_texture;
} }
if (self->biome_data) { switch (state->block) {
/* we have data, so use it! */ case 2:
unsigned int index; /* grass */
color_table = self->grasscolor;
break;
case 8:
case 9:
/* water */
color_table = self->watercolor;
break;
case 18:
/* leaves */
color_table = self->foliagecolor;
if (state->block_data == 2)
{
/* birch!
birch foliage color is flipped XY-ways */
flip_xy = 1;
}
break;
case 31:
/* tall grass */
color_table = self->grasscolor;
break;
case 104:
/* pumpkin stem */
color_table = self->grasscolor;
break;
case 105:
/* melon stem */
color_table = self->grasscolor;
break;
case 106:
/* vines */
color_table = self->grasscolor;
break;
case 111:
/* lily pads */
color_table = self->grasscolor;
break;
default:
break;
};
if (color_table) {
int dx, dz;
unsigned char tablex, tabley;
float temp = 0.0, rain = 0.0;
PyObject *color = NULL; PyObject *color = NULL;
index = big_endian_ushort(getArrayShort2D(self->biome_data, state->x, state->y)); /* average over all neighbors */
for (dx = -1; dx <= 1; dx++) {
switch (state->block) { for (dz = -1; dz <= 1; dz += (dx == 0 ? 2 : 1)) {
case 2: unsigned char biome = get_data(state, BIOMES, state->x + dx, state->y, state->z + dz);
/* grass */ if (biome > NUM_BIOMES)
color = PySequence_GetItem(self->grasscolor, index); biome = 0;
break;
case 8:
case 9:
/* water */
if (self->watercolor)
{
color = PySequence_GetItem(self->watercolor, index);
} else {
color = NULL;
facemask = NULL;
}
break;
case 18:
/* leaves */
if (state->block_data != 2)
{
/* not birch! */
color = PySequence_GetItem(self->foliagecolor, index);
} else {
/* birch!
birch foliage color is flipped XY-ways */
unsigned int index_x = 255 - (index % 256);
unsigned int index_y = 255 - (index / 256);
index = index_y * 256 + index_x;
color = PySequence_GetItem(self->foliagecolor, index); temp += biome_table[biome].temperature;
rain += biome_table[biome].rainfall;
} }
break;
case 31:
/* tall grass */
color = PySequence_GetItem(self->grasscolor, index);
break;
case 104:
/* pumpkin stem */
color = PySequence_GetItem(self->grasscolor, index);
break;
case 105:
/* melon stem */
color = PySequence_GetItem(self->grasscolor, index);
break;
case 106:
/* vines */
color = PySequence_GetItem(self->grasscolor, index);
break;
case 111:
/* lily pads */
color = PySequence_GetItem(self->grasscolor, index);
break;
default:
break;
};
if (color)
{
/* we've got work to do */
r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0));
g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1));
b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2));
Py_DECREF(color);
} }
} else { temp /= 8.0;
if (state->block == 2 || state->block == 31 || rain /= 8.0;
state->block == 104 || state->block == 105)
/* grass and pumpkin/melon stems */ /* make sure they're sane */
{ temp = CLAMP(temp, 0.0, 1.0);
r = 115; rain = CLAMP(rain, 0.0, 1.0);
g = 175;
b = 71; /* convert to x/y coordinates in color table */
tablex = 255 - (255 * temp);
tabley = 255 - (255 * temp * rain);
if (flip_xy) {
unsigned char tmp = 255 - tablex;
tablex = 255 - tabley;
tabley = tmp;
} }
if (state->block == 8 || state->block == 9) /* look up color! */
/* water */ color = PySequence_GetItem(color_table, tabley * 256 + tablex);
{ r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0));
/* by default water is fine with nothing */ g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1));
facemask = NULL; b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2));
} Py_DECREF(color);
if (state->block == 18 || state->block == 106 || state->block == 111)
/* leaves, vines and lyli pads */
{
r = 37;
g = 118;
b = 25;
}
} }
if (facemask) /* final coloration */
tint_with_mask(state->img, r, g, b, 255, facemask, state->imgx, state->imgy, 0, 0); tint_with_mask(state->img, r, g, b, 255, facemask, state->imgx, state->imgy, 0, 0);
} }
} }
@@ -231,6 +249,6 @@ RenderPrimitiveInterface primitive_base = {
base_start, base_start,
base_finish, base_finish,
base_occluded, base_occluded,
base_hidden, NULL,
base_draw, base_draw,
}; };

View File

@@ -32,14 +32,11 @@ This module has routines for extracting information about available worlds
class ChunkDoesntExist(Exception): class ChunkDoesntExist(Exception):
pass pass
class BiomeDataDoesntExist(Exception):
pass
def log_other_exceptions(func): def log_other_exceptions(func):
"""A decorator that prints out any errors that are not """A decorator that prints out any errors that are not
ChunkDoesntExist or BiomeDataDoesntExist errors. This decorates ChunkDoesntExist errors. This decorates get_chunk because the C
get_chunk because the C code is likely to swallow exceptions, so code is likely to swallow exceptions, so this will at least make
this will at least make them visible. them visible.
""" """
functools.wraps(func) functools.wraps(func)
@@ -48,8 +45,6 @@ def log_other_exceptions(func):
return func(*args) return func(*args)
except ChunkDoesntExist: except ChunkDoesntExist:
raise raise
except BiomeDataDoesntExist:
raise
except Exception, e: except Exception, e:
logging.exception("%s raised this exception", func.func_name) logging.exception("%s raised this exception", func.func_name)
raise raise
@@ -239,7 +234,6 @@ class RegionSet(object):
# Caching implementaiton: a simple LRU cache # Caching implementaiton: a simple LRU cache
# Decorate the getter methods with the cache decorator # Decorate the getter methods with the cache decorator
self._get_biome_data_for_region = cache.lru_cache(cachesize)(self._get_biome_data_for_region)
self.get_chunk = cache.lru_cache(cachesize)(self.get_chunk) self.get_chunk = cache.lru_cache(cachesize)(self.get_chunk)
# Re-initialize upon unpickling # Re-initialize upon unpickling
@@ -264,45 +258,6 @@ class RegionSet(object):
else: else:
raise Exception("Woah, what kind of dimension is this! %r" % self.regiondir) raise Exception("Woah, what kind of dimension is this! %r" % self.regiondir)
# this is decorated with cache.lru_cache in __init__(). Be aware!
@log_other_exceptions
def _get_biome_data_for_region(self, regionx, regionz):
"""Get the block of biome data for an entire region. Biome
data is in the format output by Minecraft Biome Extractor:
http://code.google.com/p/minecraft-biome-extractor/"""
# biomes only make sense for the overworld, right now
if self.get_type() != "overworld":
raise BiomeDataDoesntExist("Biome data is not available for '%s'." % (self.get_type(),))
# biomes are, unfortunately, in a different place than regiondir
biomefile = os.path.split(self.regiondir)[0]
biomefile = os.path.join(biomefile, 'biomes', 'b.%d.%d.biome' % (regionx, regionz))
try:
with open(biomefile, 'rb') as f:
data = f.read()
if not len(data) == 512 * 512 * 2:
raise BiomeDataDoesntExist("File `%s' does not have correct size." % (biomefile,))
data = numpy.frombuffer(data, dtype=numpy.dtype(">u2"))
# reshape and transpose to get [x, z] indices
return numpy.transpose(numpy.reshape(data, (512, 512)))
except IOError:
raise BiomeDataDoesntExist("File `%s' could not be read." % (biomefile,))
@log_other_exceptions
def get_biome_data(self, x, z):
"""Get the block of biome data for the given chunk. Biome data
is returned as a 16x16 numpy array of indices into the
corresponding biome color images."""
regionx = x // 32
regionz = z // 32
blockx = (x % 32) * 16
blockz = (z % 32) * 16
region_biomes = self._get_biome_data_for_region(regionx, regionz)
return region_biomes[blockx:blockx+16,blockz:blockz+16]
# this is decorated with cache.lru_cache in __init__(). Be aware! # this is decorated with cache.lru_cache in __init__(). Be aware!
@log_other_exceptions @log_other_exceptions
def get_chunk(self, x, z): def get_chunk(self, x, z):
@@ -507,11 +462,6 @@ class RotatedRegionSet(RegionSet):
def __setstate__(self, args): def __setstate__(self, args):
self.__init__(args[0], args[1]) self.__init__(args[0], args[1])
def get_biome_data(self, x, z):
x,z = self.unrotate(x,z)
biome_data = super(RotatedRegionSet, self).get_biome_data(x,z)
return numpy.rot90(biome_data, self.north_dir)
def get_chunk(self, x, z): def get_chunk(self, x, z):
x,z = self.unrotate(x,z) x,z = self.unrotate(x,z)
chunk_data = super(RotatedRegionSet, self).get_chunk(x,z) chunk_data = super(RotatedRegionSet, self).get_chunk(x,z)