Skip to content

Added BoundsOverlay Rendermode Primitive #1336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,28 @@ BiomeOverlay

BiomeOverlay(biomes=[("Forest", (0, 255, 0)), ("Desert", (255, 0, 0))])

BoundsOverlay
Color the map according to a set of bounding boxes. With this boundaries of
clans, land ownership, towns, etc. can be realized.

This Overlay colors according to a patterns that are specified as
multiple tuples of the form ``(minx, minz, maxx, maxz)``. So
by specifying ``(0, 0, 16, 16)`` all the blocks (inclusive) within the boundary
created by these coordinates will be colored. The color is then specified as ``(r, g, b, a)``.

Example::

BoundsOverlay(bounds=[
(((0, 0, 16, 16),), (255, 160, 122, 255)),
(((17, 17, 23, 23),(24, 24, 40, 40)), (75, 0, 130, 255)),
])

In this example the blocks within (0, 0, 16, 16) will be red and
all blocks within both (17, 17, 23, 23) and (24, 24, 40, 40) will be blue.

Note the trailing comma behind the tuple, this is required if only one bounding
box is given.

Defining Custom Rendermodes
---------------------------

Expand Down
7 changes: 6 additions & 1 deletion overviewer_core/rendermodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ class StructureOverlay(Overlay):
(((0, 0, 0, 157), (0, -1, 0, 4)), (255, 100, 0, 255)),
]),
}


class BoundsOverlay(Overlay):
name = "overlay-bounds"
options = {
'bounds': ('a list of (((minx, minz, maxx, maxz), ...), (r, g, b, a)) tuples for coloring boundaries', None),
}

class MineralOverlay(Overlay):
name = "overlay-mineral"
Expand Down
249 changes: 249 additions & 0 deletions overviewer_core/src/primitives/overlay-bounds.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/

#include "overlay.h"

typedef enum { false, true } bool;

typedef struct {
/* inherits from overlay */
RenderPrimitiveOverlay parent;
void *bounds;
int numcolors;
} RenderPrimitiveBounds;

struct Condition{
int minx, minz, maxx, maxz;
};


struct Color {
int numconds;
struct Condition *conditions;
unsigned char r, g, b, a;
};

/*
* Determines if point x,z is inside bounds minx, minz, maxx, maxz
*/
static int is_inside(int x, int z, int minx, int minz, int maxx, int maxz) {
return (x >= minx && x <= maxx && z >= minz && z <= maxz);
}

static void get_color(void *data,
RenderState *state,
unsigned char *r,
unsigned char *g,
unsigned char *b,
unsigned char *a) {
/**
* Calculate the color at the current position and store the values to r,g,b,a.
**/
RenderPrimitiveBounds *self = (RenderPrimitiveBounds *)data;
int x = (state->chunkx * 16) + state->x, z = (state->chunkz * 16) + state->z, col, cond;
struct Color *bounds = (struct Color *)(self->bounds);
struct Condition * c = NULL;
bool any = true;

/**
* Check for every color in the current point is in the given bounds,
* and color appropriately
**/
// iterate over all the colors
for ( col = 0; col < self->numcolors; col++) {
any = false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code format need more care here and over the whole PR.

// iterate over all conditions
for (cond = 0; cond < bounds[col].numconds; cond++) {
c = (struct Condition *)&bounds[col].conditions[cond];
// check current point is in the condition
if(is_inside(x, z, c->minx, c->minz, c->maxx, c->maxz)) {
any = true;
}
}

//if current point is in any of the conditions, draw it this color
if (any) {
// set the color
*r = bounds[col].r;
*g = bounds[col].g;
*b = bounds[col].b;
*a = bounds[col].a;
return;
}
}
return;
}

static int overlay_bounds_start(void *data, RenderState *state, PyObject *support) {
/**
* Initializing the search for bounds by parsing the arguments and storing them into
* appropriate bounds. If no arguments are passed create and use default values.
**/
PyObject *opt;
RenderPrimitiveBounds* self;

/* first, chain up */
int ret = primitive_overlay.start(data, state, support);
if (ret != 0) {
return ret;
}

/* now do custom initializations */
self = (RenderPrimitiveBounds *)data;

// opt is a borrowed reference. do not deref
// store the bounds python object into opt.
if (!render_mode_parse_option(support, "bounds", "O", &(opt))) {
return 1;
}

/**
* Check if a sane option was passed.
**/
if (opt && opt != Py_None) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if is taking whole function with exception for one command at the end. I would invert it and close function fast. By example:

if (!opt || opt == Py_None) {
  self->parent.get_color = get_color;
  return 0;
}

There is one line which will be repeated, but overall this will increase code readability with limited number of levels.

struct Color *bounds = NULL;
struct Condition *cond = NULL;
Py_ssize_t bounds_size = 0, i, cond_size = 0, n = 0;
bool cont = true;

opt = PySequence_Fast(opt, "expected a sequence");
if (!opt) {
PyErr_SetString(PyExc_TypeError, "'bounds' must be a a sequence");
return 1;
}

bounds_size = PySequence_Fast_GET_SIZE(opt);
// Getting space on the heap and do not forget to set self->numcolors.
bounds = self->bounds = calloc(bounds_size, sizeof(struct Color));
self->numcolors = bounds_size;
if (bounds == NULL) {
PyErr_SetString(PyExc_MemoryError, "failed to allocate memory");
return 1;
}

/**
* Try to parse the definitions of conditions and colors.
**/
if (cont) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar situation here, if not cond set parent color and return 0. There is no need to wait with this till the function end.

for (i = 0; i < bounds_size; i++) {
PyObject *bound = PyList_GET_ITEM(opt, i);
// condspy holding the conditions tuple of variable length (python object)
PyObject *condspy;
// colorpy holding the 4 tuple with r g b a values of the color
PyObject *colorpy;

// getting the condspy and colorpy out of the bounds.
if (!PyArg_ParseTuple(bound, "OO", &condspy, &colorpy)) {
// Exception set automatically
free(bounds);
self->bounds = NULL;
return 1;
}

// Parse colorpy into a c-struct.
if (!PyArg_ParseTuple( colorpy, "bbbb",
&bounds[i].r,
&bounds[i].g,
&bounds[i].b,
&bounds[i].a)) {
free(bounds);
self->bounds = NULL;
return 1;
}

// Convert condspy to a fast sequence
condspy = PySequence_Fast(condspy, "Failed to parse conditions");
if(condspy == NULL) {
free(bounds);
self->bounds = NULL;
return 1;
}

// get the number of conditions.
bounds[i].numconds = PySequence_Fast_GET_SIZE(condspy);
// reserve enough memory for the conditions.
cond = calloc(bounds[i].numconds, sizeof(struct Condition));
bounds[i].conditions = cond;

if (bounds[i].conditions == NULL) {
PyErr_SetString(PyExc_MemoryError, "failed to allocate memory");
free(bounds);
self->bounds = NULL;
return 1;
}

// iterate over all the conditions and read them.
for (n = 0; n < bounds[i].numconds; n++) {
PyObject *ccond = PySequence_Fast_GET_ITEM(condspy, n);
if(!PyArg_ParseTuple( ccond, "iiii",
&cond[n].minx,
&cond[n].minz,
&cond[n].maxx,
&cond[n].maxz)){
int x = 0;
for(x = 0; x < bounds_size; x++){
free(bounds[x].conditions);
}
free(bounds);
self->bounds = NULL;
return 1;
}
}
}
}
}

/* setup custom color */
self->parent.get_color = get_color;

return 0;
}

static void overlay_bounds_finish(void *data, RenderState *state) {
/* first free all *our* stuff */
RenderPrimitiveBounds* self = (RenderPrimitiveBounds *)data;
int i = 0;

if(self->bounds) {
// freeing the nested bounds
struct Color * m = self->bounds;
for(i = 0; i < self->numcolors; i++){
if(m[i].conditions) {
free(m[i].conditions);
}
}
}

if (self->bounds) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same condition in two ifs side by side, join them into one if or fail fast if there is no self->bounds.

free(self->bounds);
self->bounds = NULL;
}

/* now, chain up */
primitive_overlay.finish(data, state);
}

RenderPrimitiveInterface primitive_overlay_bounds = {
"overlay-bounds",
sizeof(RenderPrimitiveBounds),
overlay_bounds_start,
overlay_bounds_finish,
NULL,
NULL,
overlay_draw,
};