diff --git a/chunk.py b/chunk.py index a5766d6..baf9efc 100644 --- a/chunk.py +++ b/chunk.py @@ -120,7 +120,7 @@ transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53 # This set holds block ids that are solid blocks solid_blocks = set([1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 35, 41, 42, 43, 44, 45, 46, 47, 48, 49, 53, 54, 56, 57, 58, 60, - 61, 62, 64, 65, 66, 67, 71, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91, 92]) + 61, 62, 67, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91]) # This set holds block ids that are fluid blocks fluid_blocks = set([8,9,10,11]) @@ -463,7 +463,7 @@ def generate_facemasks(): return (top, left, right) facemasks = generate_facemasks() black_color = Image.new("RGB", (24,24), (0,0,0)) -red_color = Image.new("RGB", (24,24), (229,36,38)) +white_color = Image.new("RGB", (24,24), (255,255,255)) # Render 128 different color images for color coded depth blending in cave mode def generate_depthcolors(): diff --git a/config.js b/config.js index 3ec0a84..41ee9e4 100644 --- a/config.js +++ b/config.js @@ -1,6 +1,5 @@ var config = { - fileExt: '{imgformat}', tileSize: 384, defaultZoom: 2, maxZoom: {maxzoom}, @@ -33,21 +32,45 @@ var signGroups = [ {label: "All", match: function(s) {return true}}, ]; +/* regionGroups -- A list of region groups. A region can fall into zero, one, or more than one + * group. See below for some examples. + * regions have been designed to work with the + * WorldGuard Overviewer Region importer at https://github.com/pironic/WG2OvR But your host must support php in order + * to run WG2OvR. You can also continue to use any other region format. + * + * Required: + * label : string. Displayed in the drop down menu control. + * clickable : boolean. Will determine if we should generate an experimental info window + * that shows details about the clicked region. + * NOTE: if a region (as definned in region.js) does not have a label, this will default to false. + * match : function. Applied to each region (from region.js). It returns true if the region + * Should be part of the group. + * + * Optional: + * checked : boolean. Set to true to have the group visible by default + */ +var regionGroups = [ + //{label: "All", clickable: false, checked: false, match: function(s) {return true}}, +]; + /* mapTypeData -- a list of alternate map renderings available. At least one rendering must be * listed. When more than one are provided, controls to switch between them are provided, with * the first one being the default. * * Required: - * label : string. Displayed on the control. - * path : string. Location of the rendered tiles. + * label : string. Displayed on the control. + * path : string. Location of the rendered tiles. * Optional: - * base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server. + * base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server. + * imgformat : string. File extension used for these tiles. Defaults to png. + * overlay : bool. If true, this tile set will be treated like an overlay var mapTypeData=[ {'label': 'Unlit', 'path': 'tiles'}, // {'label': 'Day', 'path': 'lighting/tiles'}, -// {'label': 'Night', 'path': 'night/tiles'}, -// {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'} +// {'label': 'Night', 'path': 'night/tiles', 'imgformat': 'jpg'}, +// {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'}, +// {'label': 'Overlay', 'path': 'overlay/tiles', 'overlay': true} ]; */ diff --git a/googlemap.py b/googlemap.py index 630101a..9e04b22 100644 --- a/googlemap.py +++ b/googlemap.py @@ -23,6 +23,7 @@ from time import strftime, gmtime import json import util +from c_overviewer import get_render_mode_inheritance """ This module has routines related to generating a Google Maps-based @@ -73,12 +74,11 @@ class MapGen(object): raise ValueError("there must be at least one quadtree to work on") self.destdir = quadtrees[0].destdir - self.imgformat = quadtrees[0].imgformat self.world = quadtrees[0].world self.p = quadtrees[0].p for i in quadtrees: - if i.destdir != self.destdir or i.imgformat != self.imgformat or i.world != self.world: - raise ValueError("all the given quadtrees must have the same destdir") + if i.destdir != self.destdir or i.world != self.world: + raise ValueError("all the given quadtrees must have the same destdir and world") self.quadtrees = quadtrees @@ -86,14 +86,11 @@ class MapGen(object): """Writes out config.js, marker.js, and region.js Copies web assets into the destdir""" zoomlevel = self.p - imgformat = self.imgformat configpath = os.path.join(util.get_program_path(), "config.js") config = open(configpath, 'r').read() config = config.replace( "{maxzoom}", str(zoomlevel)) - config = config.replace( - "{imgformat}", str(imgformat)) config = config.replace("{spawn_coords}", json.dumps(list(self.world.spawn))) @@ -102,7 +99,10 @@ class MapGen(object): # create generated map type data, from given quadtrees maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), - 'path' : q.tiledir}, self.quadtrees) + 'path' : q.tiledir, + 'overlay' : 'overlay' in get_render_mode_inheritance(q.rendermode), + 'imgformat' : q.imgformat}, + self.quadtrees) config = config.replace("{maptypedata}", json.dumps(maptypedata)) with open(os.path.join(self.destdir, "config.js"), 'w') as output: @@ -114,7 +114,7 @@ class MapGen(object): for quadtree in self.quadtrees: tileDir = os.path.join(self.destdir, quadtree.tiledir) if not os.path.exists(tileDir): os.mkdir(tileDir) - blank.save(os.path.join(tileDir, "blank."+self.imgformat)) + blank.save(os.path.join(tileDir, "blank."+quadtree.imgformat)) # copy web assets into destdir: mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir) diff --git a/quadtree.py b/quadtree.py index 48d816f..58fef1b 100644 --- a/quadtree.py +++ b/quadtree.py @@ -34,6 +34,7 @@ from PIL import Image import nbt import chunk +from c_overviewer import get_render_mode_inheritance from optimizeimages import optimize_image import composite @@ -62,11 +63,11 @@ class QuadtreeGen(object): self.imgformat = imgformat self.optimizeimg = optimizeimg self.bgcolor = bgcolor - - self.lighting = rendermode in ("lighting", "night", "spawn") - self.night = rendermode in ("night", "spawn") - self.spawn = rendermode in ("spawn",) self.rendermode = rendermode + + # force png renderformat if we're using an overlay mode + if 'overlay' in get_render_mode_inheritance(rendermode): + self.imgformat = "png" # Make the destination dir if not os.path.exists(destdir): diff --git a/setup.py b/setup.py index a03fee6..5eb8c23 100644 --- a/setup.py +++ b/setup.py @@ -54,10 +54,14 @@ try: except: pil_include = [] -c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c'] -c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c', 'src/rendermode-cave.c'] +# used to figure out what files to compile +render_modes = ['normal', 'overlay', 'lighting', 'night', 'spawn', 'cave'] + +c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c', 'src/rendermodes.c'] +c_overviewer_files += map(lambda mode: 'src/rendermode-%s.c' % (mode,), render_modes) c_overviewer_files += ['src/Draw.c'] c_overviewer_includes = ['src/overviewer.h', 'src/rendermodes.h'] + setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include] + pil_include, depends=c_overviewer_includes, extra_link_args=[])) # tell build_ext to build the extension in-place diff --git a/src/composite.c b/src/composite.c index 4a03d23..432ece8 100644 --- a/src/composite.c +++ b/src/composite.c @@ -273,7 +273,8 @@ alpha_over_wrap(PyObject *self, PyObject *args) * also, it multiplies instead of doing an over operation */ PyObject * -tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, +tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, + unsigned char sb, unsigned char sa, PyObject *mask, int dx, int dy, int xsize, int ysize) { /* libImaging handles */ Imaging imDest, imMask; @@ -332,9 +333,11 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char out++; *out = MULDIV255(*out, sb, tmp1); out++; + *out = MULDIV255(*out, sa, tmp1); + out++; } else if (*inmask == 0) { /* do nothing -- source is fully transparent */ - out += 3; + out += 4; } else { /* general case */ @@ -345,9 +348,10 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char out++; *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sb, *inmask, tmp1), tmp2); out++; + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sa, *inmask, tmp1), tmp2); + out++; } - out++; inmask += mask_stride; } } diff --git a/src/main.c b/src/main.c index ddbe0af..4de7eec 100644 --- a/src/main.c +++ b/src/main.c @@ -25,14 +25,24 @@ PyObject *get_extension_version(PyObject *self, PyObject *args) { static PyMethodDef COverviewerMethods[] = { {"alpha_over", alpha_over_wrap, METH_VARARGS, "alpha over composite function"}, + {"render_loop", chunk_render, METH_VARARGS, "Renders stuffs"}, + {"get_render_modes", get_render_modes, METH_VARARGS, "returns available render modes"}, {"get_render_mode_info", get_render_mode_info, METH_VARARGS, "returns info for a particular render mode"}, + {"get_render_mode_parent", get_render_mode_parent, METH_VARARGS, + "returns parent for a particular render mode"}, + {"get_render_mode_inheritance", get_render_mode_inheritance, METH_VARARGS, + "returns inheritance chain for a particular render mode"}, + {"get_render_mode_children", get_render_mode_children, METH_VARARGS, + "returns (direct) children for a particular render mode"}, + {"extension_version", get_extension_version, METH_VARARGS, "Returns the extension version"}, + {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/src/overviewer.h b/src/overviewer.h index db93a67..b18462a 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -48,7 +48,8 @@ PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, PyObject *alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha, int dx, int dy, int xsize, int ysize); PyObject *alpha_over_wrap(PyObject *self, PyObject *args); -PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, +PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, + unsigned char sb, unsigned char sa, PyObject *mask, int dx, int dy, int xsize, int ysize); /* in iterate.c */ diff --git a/src/rendermode-cave.c b/src/rendermode-cave.c index ae5b565..f8334b2 100644 --- a/src/rendermode-cave.c +++ b/src/rendermode-cave.c @@ -219,12 +219,13 @@ rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *ma g = PyInt_AsLong(PyList_GetItem(self->depth_colors, 1 + z*3)); b = PyInt_AsLong(PyList_GetItem(self->depth_colors, 2 + z*3)); - tint_with_mask(state->img, r, g, b, mask, state->imgx, state->imgy, 0, 0); + tint_with_mask(state->img, r, g, b, 255, mask, state->imgx, state->imgy, 0, 0); } RenderModeInterface rendermode_cave = { "cave", "render only caves in normal mode", + &rendermode_normal, sizeof(RenderModeCave), rendermode_cave_start, rendermode_cave_finish, diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 628e5c4..83b38d3 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -229,6 +229,7 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject RenderModeInterface rendermode_lighting = { "lighting", "draw shadows from the lighting data", + &rendermode_normal, sizeof(RenderModeLighting), rendermode_lighting_start, rendermode_lighting_finish, diff --git a/src/rendermode-night.c b/src/rendermode-night.c index 1e54f05..46ca7c2 100644 --- a/src/rendermode-night.c +++ b/src/rendermode-night.c @@ -61,6 +61,7 @@ rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *m RenderModeInterface rendermode_night = { "night", "like \"lighting\", except at night", + &rendermode_lighting, sizeof(RenderModeNight), rendermode_night_start, rendermode_night_finish, diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index 1381eb0..9f139a3 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -169,7 +169,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)); Py_DECREF(color); - tint_with_mask(state->img, r, g, b, facemask, state->imgx, state->imgy, 0, 0); + tint_with_mask(state->img, r, g, b, 255, facemask, state->imgx, state->imgy, 0, 0); } } @@ -221,6 +221,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * RenderModeInterface rendermode_normal = { "normal", "nothing special, just render the blocks", + NULL, sizeof(RenderModeNormal), rendermode_normal_start, rendermode_normal_finish, diff --git a/src/rendermode-overlay.c b/src/rendermode-overlay.c new file mode 100644 index 0000000..fb4e765 --- /dev/null +++ b/src/rendermode-overlay.c @@ -0,0 +1,137 @@ +/* + * 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 . + */ + +#include "overviewer.h" + +static void get_color(void *data, RenderState *state, + unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { + *r = 200; + *g = 200; + *b = 255; + *a = 155; +} + +static int +rendermode_overlay_start(void *data, RenderState *state) { + PyObject *facemasks_py; + RenderModeOverlay *self = (RenderModeOverlay *)data; + + facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); + /* borrowed reference, needs to be incref'd if we keep it */ + self->facemask_top = PyTuple_GetItem(facemasks_py, 0); + Py_INCREF(self->facemask_top); + Py_DECREF(facemasks_py); + + self->white_color = PyObject_GetAttrString(state->chunk, "white_color"); + + self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks"); + self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks"); + + self->get_color = get_color; + + return 0; +} + +static void +rendermode_overlay_finish(void *data, RenderState *state) { + RenderModeOverlay *self = (RenderModeOverlay *)data; + + Py_DECREF(self->facemask_top); + Py_DECREF(self->white_color); + Py_DECREF(self->solid_blocks); + Py_DECREF(self->fluid_blocks); +} + +static int +rendermode_overlay_occluded(void *data, RenderState *state) { + int x = state->x, y = state->y, z = state->z; + + if ( (x != 0) && (y != 15) && (z != 127) && + !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + + return 0; +} + +static void +rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + RenderModeOverlay *self = (RenderModeOverlay *)data; + unsigned char r, g, b, a; + PyObject *top_block_py, *block_py; + + // exactly analogous to edge-line code for these special blocks + int increment=0; + if (state->block == 44) // half-step + increment=6; + else if (state->block == 78) // snow + increment=9; + + /* clear the draw space -- set alpha to 0 within mask */ + tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); + + /* skip rendering the overlay if we can't see it */ + if (state->z != 127) { + unsigned char top_block = getArrayByte3D(state->blocks, state->x, state->y, state->z+1); + if (!is_transparent(top_block)) { + return; + } + + /* check to be sure this block is solid/fluid */ + top_block_py = PyInt_FromLong(top_block); + if (PySequence_Contains(self->solid_blocks, top_block_py) || + PySequence_Contains(self->fluid_blocks, top_block_py)) { + + /* top block is fluid or solid, skip drawing */ + Py_DECREF(top_block_py); + return; + } + Py_DECREF(top_block_py); + } + + /* check to be sure this block is solid/fluid */ + block_py = PyInt_FromLong(state->block); + if (!PySequence_Contains(self->solid_blocks, block_py) && + !PySequence_Contains(self->fluid_blocks, block_py)) { + + /* not fluid or solid, skip drawing the overlay */ + Py_DECREF(block_py); + return; + } + Py_DECREF(block_py); + + /* get our color info */ + self->get_color(data, state, &r, &g, &b, &a); + + /* do the overlay */ + if (a > 0) { + alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy + increment, 0, 0); + tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy + increment, 0, 0); + } +} + +RenderModeInterface rendermode_overlay = { + "overlay", "base rendermode for informational overlays", + NULL, + sizeof(RenderModeOverlay), + rendermode_overlay_start, + rendermode_overlay_finish, + rendermode_overlay_occluded, + rendermode_overlay_draw, +}; diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index b19f409..498ed17 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -18,21 +18,66 @@ #include "overviewer.h" #include +static void get_color(void *data, RenderState *state, + unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { + + RenderModeSpawn* self = (RenderModeSpawn *)data; + int x = state->x, y = state->y, z = state->z; + int z_light = z + 1; + unsigned char blocklight, skylight; + PyObject *block_py; + + /* set a nice, pretty red color */ + *r = 229; + *g = 36; + *b = 38; + + /* default to no overlay, until told otherwise */ + *a = 0; + + block_py = PyInt_FromLong(state->block); + if (PySequence_Contains(self->nospawn_blocks, block_py)) { + /* nothing can spawn on this */ + Py_DECREF(block_py); + return; + } + Py_DECREF(block_py); + + blocklight = getArrayByte3D(self->blocklight, x, y, MIN(127, z_light)); + + /* if we're at the top, force 15 (brightest!) skylight */ + if (z_light == 128) { + skylight = 15; + } else { + skylight = getArrayByte3D(self->skylight, x, y, z_light); + } + + if (MAX(blocklight, skylight) <= 7) { + /* hostile mobs spawn in daylight */ + *a = 240; + } else if (MAX(blocklight, skylight - 11) <= 7) { + /* hostile mobs spawn at night */ + *a = 150; + } +} + static int rendermode_spawn_start(void *data, RenderState *state) { RenderModeSpawn* self; /* first, chain up */ - int ret = rendermode_night.start(data, state); + int ret = rendermode_overlay.start(data, state); if (ret != 0) return ret; /* now do custom initializations */ self = (RenderModeSpawn *)data; - self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks"); self->nospawn_blocks = PyObject_GetAttrString(state->chunk, "nospawn_blocks"); - self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks"); - self->red_color = PyObject_GetAttrString(state->chunk, "red_color"); + self->blocklight = PyObject_GetAttrString(state->self, "blocklight"); + self->skylight = PyObject_GetAttrString(state->self, "skylight"); + + /* setup custom color */ + self->parent.get_color = get_color; return 0; } @@ -40,85 +85,31 @@ rendermode_spawn_start(void *data, RenderState *state) { static void rendermode_spawn_finish(void *data, RenderState *state) { /* first free all *our* stuff */ - RenderModeSpawn* self = (RenderModeSpawn *)data; + RenderModeSpawn* self = (RenderModeSpawn *)data; - Py_DECREF(self->solid_blocks); Py_DECREF(self->nospawn_blocks); - Py_DECREF(self->fluid_blocks); + Py_DECREF(self->blocklight); + Py_DECREF(self->skylight); /* now, chain up */ - rendermode_night.finish(data, state); + rendermode_overlay.finish(data, state); } static int rendermode_spawn_occluded(void *data, RenderState *state) { /* no special occlusion here */ - return rendermode_night.occluded(data, state); + return rendermode_overlay.occluded(data, state); } static void rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { - /* different versions of self (spawn, lighting) */ - RenderModeSpawn* self = (RenderModeSpawn *)data; - RenderModeLighting *lighting = (RenderModeLighting *)self; - - int x = state->x, y = state->y, z = state->z; - PyObject *old_black_color = NULL; - - /* figure out the appropriate darkness: - this block for transparents, the block above for non-transparent */ - float darkness = 0.0; - if (is_transparent(state->block)) { - darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z, NULL); - } else { - darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z+1, NULL); - } - - /* if it's dark enough... */ - if (darkness > 0.8) { - PyObject *block_py = PyInt_FromLong(state->block); - - /* make sure it's solid */ - if (PySequence_Contains(self->solid_blocks, block_py)) { - int spawnable = 1; - - /* not spawnable if its in the nospawn list */ - if (PySequence_Contains(self->nospawn_blocks, block_py)) - spawnable = 0; - - /* check the block above for solid or fluid */ - if (spawnable && z != 127) { - PyObject *top_block_py = PyInt_FromLong(getArrayByte3D(state->blocks, x, y, z+1)); - if (PySequence_Contains(self->solid_blocks, top_block_py) || - PySequence_Contains(self->fluid_blocks, top_block_py)) { - - spawnable = 0; - } - - Py_DECREF(top_block_py); - } - - /* if we passed all the checks, replace black_color with red_color */ - if (spawnable) { - old_black_color = lighting->black_color; - lighting->black_color = self->red_color; - } - } - - Py_DECREF(block_py); - } - /* draw normally */ - rendermode_night.draw(data, state, src, mask); - - /* reset black_color, if needed */ - if (old_black_color != NULL) { - lighting->black_color = old_black_color; - } + rendermode_overlay.draw(data, state, src, mask); } RenderModeInterface rendermode_spawn = { - "spawn", "draws red where monsters can spawn at night", + "spawn", "draws a red overlay where monsters can spawn at night", + &rendermode_overlay, sizeof(RenderModeSpawn), rendermode_spawn_start, rendermode_spawn_finish, diff --git a/src/rendermodes.c b/src/rendermodes.c index 08ac140..43658e1 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -98,5 +98,87 @@ PyObject *get_render_mode_info(PyObject *self, PyObject *args) { } Py_DECREF(info); - Py_RETURN_NONE; + return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode); +} + +/* bindings -- get parent's name */ +PyObject *get_render_mode_parent(PyObject *self, PyObject *args) { + const char *rendermode; + unsigned int i; + if (!PyArg_ParseTuple(args, "s", &rendermode)) + return NULL; + + for (i = 0; render_modes[i] != NULL; i++) { + if (strcmp(render_modes[i]->name, rendermode) == 0) { + if (render_modes[i]->parent) { + /* has parent */ + return PyString_FromString(render_modes[i]->parent->name); + } else { + /* no parent */ + Py_RETURN_NONE; + } + } + } + + return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode); +} + +/* bindings -- get list of inherited parents */ +PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args) { + const char *rendermode; + PyObject *parents; + unsigned int i; + RenderModeInterface *iface = NULL; + if (!PyArg_ParseTuple(args, "s", &rendermode)) + return NULL; + + parents = PyList_New(0); + if (!parents) + return NULL; + + for (i = 0; render_modes[i] != NULL; i++) { + if (strcmp(render_modes[i]->name, rendermode) == 0) { + iface = render_modes[i]; + break; + } + } + + if (!iface) { + Py_DECREF(parents); + return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode); + } + + while (iface) { + PyObject *name = PyString_FromString(iface->name); + PyList_Append(parents, name); + Py_DECREF(name); + + iface = iface->parent; + } + + PyList_Reverse(parents); + return parents; +} + +/* bindings -- get list of (direct) children */ +PyObject *get_render_mode_children(PyObject *self, PyObject *args) { + const char *rendermode; + PyObject *children; + unsigned int i; + if (!PyArg_ParseTuple(args, "s", &rendermode)) + return NULL; + + children = PyList_New(0); + if (!children) + return NULL; + + for (i = 0; render_modes[i] != NULL; i++) { + if (render_modes[i]->parent && strcmp(render_modes[i]->parent->name, rendermode) == 0) { + PyObject *child_name = PyString_FromString(render_modes[i]->name); + PyList_Append(children, child_name); + Py_DECREF(child_name); + } + } + + return children; } diff --git a/src/rendermodes.h b/src/rendermodes.h index 70e9c46..fd0b479 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -38,12 +38,15 @@ #include /* rendermode interface */ -typedef struct { +typedef struct _RenderModeInterface RenderModeInterface; +struct _RenderModeInterface { /* the name of this mode */ const char* name; /* the short description of this render mode */ const char* description; + /* the rendermode this is derived from, or NULL */ + RenderModeInterface *parent; /* the size of the local storage for this rendermode */ unsigned int data_size; @@ -54,13 +57,16 @@ typedef struct { int (*occluded)(void *, RenderState *); /* last two arguments are img and mask, from texture lookup */ void (*draw)(void *, RenderState *, PyObject *, PyObject *); -} RenderModeInterface; +}; /* figures out the render mode to use from the given ChunkRenderer */ RenderModeInterface *get_render_mode(RenderState *state); /* python bindings */ PyObject *get_render_modes(PyObject *self, PyObject *args); PyObject *get_render_mode_info(PyObject *self, PyObject *args); +PyObject *get_render_mode_parent(PyObject *self, PyObject *args); +PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args); +PyObject *get_render_mode_children(PyObject *self, PyObject *args); /* individual rendermode interface declarations follow */ @@ -79,6 +85,20 @@ typedef struct { } RenderModeNormal; extern RenderModeInterface rendermode_normal; +/* OVERLAY */ +typedef struct { + /* top facemask and white color image, for drawing overlays */ + PyObject *facemask_top, *white_color; + /* only show overlay on top of solid or fluid blocks */ + PyObject *solid_blocks, *fluid_blocks; + /* can be overridden in derived classes to control + overlay alpha and color + last four vars are r, g, b, a out */ + void (*get_color)(void *, RenderState *, + unsigned char *, unsigned char *, unsigned char *, unsigned char *); +} RenderModeOverlay; +extern RenderModeInterface rendermode_overlay; + /* LIGHTING */ typedef struct { /* inherits from normal render mode */ @@ -109,13 +129,12 @@ extern RenderModeInterface rendermode_night; /* SPAWN */ typedef struct { - /* inherits from night */ - RenderModeNight parent; + /* inherits from overlay */ + RenderModeOverlay parent; /* used to figure out which blocks are spawnable */ - PyObject *solid_blocks, *nospawn_blocks, *fluid_blocks; - /* replacement for black_color */ - PyObject *red_color; + PyObject *nospawn_blocks; + PyObject *skylight, *blocklight; } RenderModeSpawn; extern RenderModeInterface rendermode_spawn; diff --git a/web_assets/functions.js b/web_assets/functions.js index 3e09788..b8ace5e 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -1,14 +1,16 @@ +// var def +var map; // god of the overviewer... bow before the google api. var markerCollection = {}; // holds groups of markers - -var map; - -var markersInit = false; -var regionsInit = false; +var markersInit = false; // only have to load the markers once, this just makes sure we only do it once +var regionCollection = {}; // holds groups of regions +var regionsInit = false; // only have to load the regions once, this just makes sure we only do it once var prevInfoWindow = null; +// add a popup info window to the marker and then the marker to the map. +// marker is the clickable image on the map with all data. +// item is just the same item in the markers.js function prepareSignMarker(marker, item) { - var c = "

" + item.msg.replace(/\n/g,"
") + "

"; var infowindow = new google.maps.InfoWindow({content: c }); @@ -17,154 +19,289 @@ function prepareSignMarker(marker, item) { prevInfoWindow.close() infowindow.open(map,marker); prevInfoWindow = infowindow - }); - + }); } +// reusable function for making drop down menus. +// title = string +// items = array +function createDropDown(title, items) { + var control = document.createElement("DIV"); + control.id = "customControl"; // let's let a style sheet do most of the styling here -function drawMapControls() { - - // viewstate link - var viewStateDiv = document.createElement('DIV'); - - // - - viewStateDiv.id="link"; - - - map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push(viewStateDiv); - - - // compass rose, in the top right corner - var compassDiv = document.createElement('DIV'); - - compassDiv.style.padding = '5px'; - - var compassImg = document.createElement('IMG'); - compassImg.src="compass.png"; - compassDiv.appendChild(compassImg); - - compassDiv.index = 0; - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); - - - if (signGroups.length > 0) { - // signpost display control - // - - var signControl = document.createElement("DIV"); - signControl.id = "signControl"; // let's let a style sheet do most of the styling here + var controlText = document.createElement("DIV"); + controlText.innerHTML = title; var controlBorder = document.createElement("DIV"); controlBorder.id="top"; - signControl.appendChild(controlBorder); - - var controlText = document.createElement("DIV"); - + control.appendChild(controlBorder); controlBorder.appendChild(controlText); - controlText.innerHTML = "Signposts"; - var dropdownDiv = document.createElement("DIV"); - - - $(controlText).click(function() { - $(dropdownDiv).toggle(); - - }); - - dropdownDiv.id="dropDown"; - signControl.appendChild(dropdownDiv); + control.appendChild(dropdownDiv); dropdownDiv.innerHTML=""; - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(signControl); + // add the functionality to toggle visibility of the items + $(controlText).click(function() { + $(dropdownDiv).toggle(); + }); + + // add that control box we've made back to the map. + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control); - - - var hasSignGroup = false; - for (idx in signGroups) { - var item = signGroups[idx]; - //console.log(item); - label = item.label; - hasSignGroup = true; + for (idx in items) { + // create the visible elements of the item + var item = items[idx]; + //console.log(item); // debug var d = document.createElement("div"); var n = document.createElement("input"); n.type="checkbox"; - $(n).data("label",label); - jQuery(n).click(function(e) { - var t = $(e.target); - jQuery.each(markerCollection[t.data("label")], function(i,elem) {elem.setVisible(e.target.checked);}); - }); - + // give it a name + $(n).data("label",item.label); + jQuery(n).click((function(local_idx, local_item) { + return function(e) { + item.action(local_idx, local_item, e.target.checked); + }; + })(idx, item)); + // if its checked, its gotta do something, do that here. if (item.checked) { n.checked = true; - jQuery.each(markerCollection[label], function(i,elem) {elem.setVisible(n.checked);}); + item.action(idx, item.label, item.checked); } dropdownDiv.appendChild(d); d.appendChild(n) var textNode = document.createElement("text"); - textNode.innerHTML = label + "
"; + if(item.icon) { + textNode.innerHTML = "" + item.label + "
"; + } else { + textNode.innerHTML = item.label + "
"; + } + d.appendChild(textNode); - - } - - } } +function HomeControl(controlDiv, map) { + + controlDiv.style.padding = '5px'; + + // Set CSS for the control border + var control = document.createElement('DIV'); + control.id='top'; + control.title = 'Click to center the map on the Spawn'; + controlDiv.appendChild(control); + + // Set CSS for the control interior + var controlText = document.createElement('DIV'); + controlText.innerHTML = 'Spawn'; + controlText.id='button'; + control.appendChild(controlText); + + // Setup the click event listeners: simply set the map to map center as definned below + google.maps.event.addDomListener(control, 'click', function() { + map.panTo(fromWorldToLatLng(config.center[0], + config.center[1], + config.center[2])); + }); + +} + + +// need to define the controls including the compass and layer controls. top right! +// input variables are for chumps... and reusable functions. this is neither. +function drawMapControls() { + + // viewstate link (little link to where you're looking at the map, normally bottom left) + var viewStateDiv = document.createElement('DIV'); + viewStateDiv.id="link"; + // add it to the map, bottom left. + map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); + + // compass rose, in the top right corner + var compassDiv = document.createElement('DIV'); + compassDiv.style.padding = '5px'; + var compassImg = document.createElement('IMG'); + compassImg.src="compass.png"; + compassDiv.appendChild(compassImg); + compassDiv.index = 0; + // add it to the map, top right. + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + + // Spawn button + var homeControlDiv = document.createElement('DIV'); + var homeControl = new HomeControl(homeControlDiv, map); + homeControlDiv.id = "customControl"; + homeControlDiv.index = 1; + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); + + + // only need to create the control if there are items in the list. as definned in config.js + if (signGroups.length > 0) { + // signpost display control + + var items = []; + for (idx in signGroups) { + var signGroup = signGroups[idx]; + var iconURL = signGroup.icon; + if (!iconURL) { iconURL = 'signpost.png'; } + items.push({ + "label": signGroup.label, + "checked": signGroup.checked, + "icon": iconURL, + "action": function(n, item, checked) { + jQuery.each(markerCollection[item.label], function(i,elem) { + elem.setVisible(checked); + }); + //alert(item.label); + } + }); + } + createDropDown("Signposts", items); + } + + // if there are any regions data, lets show the option to hide/show them. + if (regionGroups.length > 0) { + // region display control + + var items = []; + for (idx in regionGroups) { + var regionGroup = regionGroups[idx]; + items.push({ + "label": regionGroup.label, + "checked": regionGroup.checked, + "action": function(n, item, checked) { + jQuery.each(regionCollection[item.label], function(i,elem) { + elem.setMap(checked ? map : null); // Thanks to LeastWeasel for this line! + }); + } + }); + } + createDropDown("Regions", items); + } + + if (overlayMapTypes.length > 0) { + // overlay maps control + + var items = []; + for (idx in overlayMapTypes) { + var overlay = overlayMapTypes[idx]; + items.push({"label": overlay.name, "checked": false, "overlay": overlay, + "action": function(i, item, checked) { + if (checked) { + map.overlayMapTypes.push(item.overlay); + } else { + var idx_to_delete = -1; + map.overlayMapTypes.forEach(function(e, j) { + if (e == item.overlay) { idx_to_delete = j; } + }); + if (idx_to_delete >= 0) { + map.overlayMapTypes.removeAt(idx_to_delete); + } + } + }}); + } + createDropDown("Overlays", items); + } +} + +// will be recoded by pi, currently always displays all regions all the time. +// parse the data as definned in the regions.js function initRegions() { if (regionsInit) { return; } - regionsInit = true; + + for (i in regionGroups) { + regionCollection[regionGroups[i].label] = []; + } for (i in regionData) { var region = regionData[i]; + + // pull all the points out of the regions file. var converted = new google.maps.MVCArray(); + var infoPoint = ""; for (j in region.path) { var point = region.path[j]; converted.push(fromWorldToLatLng(point.x, point.y, point.z)); + } + + for (idx in regionGroups) { + var regionGroup = regionGroups[idx]; + var testfunc = regionGroup.match; + var clickable = regionGroup.clickable + var label = regionGroup.label; + + if(region.label) { + var name = region.label + } else { + var name = 'rawr'; + clickable = false; // if it doesn't have a name, we dont have to show it. + } - if (region.closed) { - new google.maps.Polygon({clickable: false, - geodesic: false, - map: map, - strokeColor: region.color, - strokeOpacity: region.opacity, - strokeWeight: 2, - fillColor: region.color, - fillOpacity: region.opacity * 0.25, - zIndex: i, - paths: converted + if (region.closed) { + var shape = new google.maps.Polygon({ + name: name, + clickable: clickable, + geodesic: false, + map: null, + strokeColor: region.color, + strokeOpacity: region.opacity, + strokeWeight: 2, + fillColor: region.color, + fillOpacity: region.opacity * 0.25, + zIndex: i, + paths: converted }); - } else { - new google.maps.Polyline({clickable: false, - geodesic: false, - map: map, - strokeColor: region.color, - strokeOpacity: region.opacity, - strokeWeight: 2, - zIndex: i, - path: converted + } else { + var shape = new google.maps.Polyline({ + name: name, + clickable: clickable, + geodesic: false, + map: null, + strokeColor: region.color, + strokeOpacity: region.opacity, + strokeWeight: 2, + zIndex: i, + path: converted }); + } + regionCollection[label].push(shape); + + if (clickable) { + // add the region infowindow popup + infowindow = new google.maps.InfoWindow(); + google.maps.event.addListener(shape, 'click', function(e,i) { + + var contentString = "Region: "+this.name+"
"; + contentString += "Clicked Location:
" + e.latLng.lat() + "," + e.latLng.lng() + "
"; + + // Replace our Info Window's content and position + infowindow.setContent(contentString); + infowindow.setPosition(e.latLng); + + infowindow.open(map); + + }); + } } } } - - +// will initalize all the markers data as found in markers.js +// may need to be reviewed by agrif or someone else... little finicky right now. function initMarkers() { - if (markersInit) { return; } - - markersInit = true; + if (markersInit) { return; } // oh, we've already done this? nevermind, exit the function. + markersInit = true; // now that we've started, dont have to do it twice. // first, give all collections an empty array to work with for (i in signGroups) { markerCollection[signGroups[i].label] = []; } + for (i in markerData) { var item = markerData[i]; @@ -180,8 +317,7 @@ function initMarkers() { map: map, title: jQuery.trim(item.msg), icon: iconURL - }); - + }); continue; } @@ -208,11 +344,11 @@ function initMarkers() { if (testfunc(item)) { matched = true; - if (item.type == 'sign') { iconURL = 'signpost_icon.png';} + // can add custom types of images for externally definned item types, like 'command' here. + if (item.type == 'sign') { iconURL = 'signpost_icon.png'; } - //console.log(signGroup.icon); - if (signGroup.icon) { iconURL = signGroup.icon; - } + //console.log(signGroup.icon); //debug + if (signGroup.icon) { iconURL = signGroup.icon; } var converted = fromWorldToLatLng(item.x, item.y, item.z); var marker = new google.maps.Marker({position: converted, @@ -220,19 +356,16 @@ function initMarkers() { title: jQuery.trim(item.msg), icon: iconURL, visible: false - }); + }); markerCollection[label].push(marker); if (item.type == 'sign') { prepareSignMarker(marker, item); } - } - - } - + if (!matched) { // is this signpost doesn't match any of the groups in config.js, add it automatically to the "__others__" group if (item.type == 'sign') { iconURL = 'signpost_icon.png';} @@ -254,12 +387,13 @@ function initMarkers() { prepareSignMarker(marker, item); } } + } } - +// update the link in the viewstate. function makeLink() { var displayZoom = map.getZoom(); if (displayZoom == config.maxZoom) { @@ -277,6 +411,7 @@ function makeLink() { document.getElementById("link").innerHTML = a; } +// load the map up and add all the functions relevant stuff to the map. function initialize() { var query = location.search.substring(1); @@ -365,7 +500,7 @@ function initialize() { for (idx in MCMapType) { map.mapTypes.set('mcmap' + MCMapType[idx].name, MCMapType[idx]); } - + // We can now set the map to use the 'coordinate' map type map.setMapTypeId(mapTypeIdDefault); @@ -387,69 +522,69 @@ function initialize() { } - // our custom projection maps Latitude to Y, and Longitude to X as normal, - // but it maps the range [0.0, 1.0] to [0, tileSize] in both directions - // so it is easier to position markers, etc. based on their position - // (find their position in the lowest-zoom image, and divide by tileSize) - function MCMapProjection() { +// our custom projection maps Latitude to Y, and Longitude to X as normal, +// but it maps the range [0.0, 1.0] to [0, tileSize] in both directions +// so it is easier to position markers, etc. based on their position +// (find their position in the lowest-zoom image, and divide by tileSize) +function MCMapProjection() { this.inverseTileSize = 1.0 / config.tileSize; - } +} - MCMapProjection.prototype.fromLatLngToPoint = function(latLng) { +MCMapProjection.prototype.fromLatLngToPoint = function(latLng) { var x = latLng.lng() * config.tileSize; var y = latLng.lat() * config.tileSize; return new google.maps.Point(x, y); - }; +}; - MCMapProjection.prototype.fromPointToLatLng = function(point) { +MCMapProjection.prototype.fromPointToLatLng = function(point) { var lng = point.x * this.inverseTileSize; var lat = point.y * this.inverseTileSize; return new google.maps.LatLng(lat, lng); - }; +}; - // helper to get map LatLng from world coordinates - // takes arguments in X, Y, Z order - // (arguments are *out of order*, because within the function we use - // the axes like the rest of Minecraft Overviewer -- with the Z and Y - // flipped from normal minecraft usage.) - function fromWorldToLatLng(x, z, y) - { +// helper to get map LatLng from world coordinates +// takes arguments in X, Y, Z order +// (arguments are *out of order*, because within the function we use +// the axes like the rest of Minecraft Overviewer -- with the Z and Y +// flipped from normal minecraft usage.) +function fromWorldToLatLng(x, z, y) +{ // the width and height of all the highest-zoom tiles combined, inverted var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.maxZoom)); - + // This information about where the center column is may change with a different // drawing implementation -- check it again after any drawing overhauls! - + // point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2) // so the Y coordinate is at 0.5, and the X is at 0.5 - ((tileSize / 2) / (tileSize * 2^maxZoom)) // or equivalently, 0.5 - (1 / 2^(maxZoom + 1)) var lng = 0.5 - (1.0 / Math.pow(2, config.maxZoom + 1)); var lat = 0.5; - + // the following metrics mimic those in ChunkRenderer.chunk_render in "chunk.py" // or, equivalently, chunk_render in src/iterate.c - + // each block on X axis adds 12px to x and subtracts 6px from y lng += 12 * x * perPixel; lat -= 6 * x * perPixel; - + // each block on Y axis adds 12px to x and adds 6px to y lng += 12 * y * perPixel; lat += 6 * y * perPixel; - + // each block down along Z adds 12px to y lat += 12 * (128 - z) * perPixel; // add on 12 px to the X coordinate to center our point lng += 12 * perPixel; - - return new google.maps.LatLng(lat, lng); - } - // NOTE: X, Y and Z in this function are Minecraft world definitions - // (that is, X is horizontal, Y is altitude and Z is vertical). - function fromLatLngToWorld(lat, lng) - { + return new google.maps.LatLng(lat, lng); +} + +// NOTE: X, Y and Z in this function are Minecraft world definitions +// (that is, X is horizontal, Y is altitude and Z is vertical). +function fromLatLngToWorld(lat, lng) +{ // Initialize world x/y/z object to be returned var xyz = Array(); xyz.x = 0; @@ -477,67 +612,74 @@ function initialize() { // only latitude and longitude, so assume Y=64. xyz.x += 64 + 1; xyz.z -= 64 + 2; - + return xyz; - } - -function getTileUrlGenerator(path, path_base) { - return function(tile, zoom) { - var url = path; - var url_base = ( path_base ? path_base : '' ); - if(tile.x < 0 || tile.x >= Math.pow(2, zoom) || tile.y < 0 || tile.y >= Math.pow(2, zoom)) { - url += '/blank'; - } else if(zoom == 0) { - url += '/base'; - } else { - for(var z = zoom - 1; z >= 0; --z) { - var x = Math.floor(tile.x / Math.pow(2, z)) % 2; - var y = Math.floor(tile.y / Math.pow(2, z)) % 2; - url += '/' + (x + 2 * y); +} + +function getTileUrlGenerator(path, path_base, path_ext) { + return function(tile, zoom) { + var url = path; + var url_base = ( path_base ? path_base : '' ); + if(tile.x < 0 || tile.x >= Math.pow(2, zoom) || tile.y < 0 || tile.y >= Math.pow(2, zoom)) { + url += '/blank'; + } else if(zoom == 0) { + url += '/base'; + } else { + for(var z = zoom - 1; z >= 0; --z) { + var x = Math.floor(tile.x / Math.pow(2, z)) % 2; + var y = Math.floor(tile.y / Math.pow(2, z)) % 2; + url += '/' + (x + 2 * y); + } } - } - url = url + '.' + config.fileExt; - if(config.cacheMinutes > 0) { - var d = new Date(); - url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes)); - } - return(url_base + url); - } + url = url + '.' + path_ext; + if(config.cacheMinutes > 0) { + var d = new Date(); + url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes)); + } + return(url_base + url); + } } var MCMapOptions = new Array; var MCMapType = new Array; var mapTypeIdDefault = null; var mapTypeIds = []; +var overlayMapTypes = []; for (idx in mapTypeData) { - var view = mapTypeData[idx]; + var view = mapTypeData[idx]; + var imgformat = view.imgformat ? view.imgformat : 'png'; - MCMapOptions[view.label] = { - getTileUrl: getTileUrlGenerator(view.path, view.base), - tileSize: new google.maps.Size(config.tileSize, config.tileSize), - maxZoom: config.maxZoom, - minZoom: 0, - isPng: !(config.fileExt.match(/^png$/i) == null) - }; + MCMapOptions[view.label] = { + getTileUrl: getTileUrlGenerator(view.path, view.base, imgformat), + tileSize: new google.maps.Size(config.tileSize, config.tileSize), + maxZoom: config.maxZoom, + minZoom: 0, + isPng: !(imgformat.match(/^png$/i) == null) + }; - MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]); - MCMapType[view.label].name = view.label; - MCMapType[view.label].alt = "Minecraft " + view.label + " Map"; - MCMapType[view.label].projection = new MCMapProjection(); - if (mapTypeIdDefault == null) { - mapTypeIdDefault = 'mcmap' + view.label; + MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]); + MCMapType[view.label].name = view.label; + MCMapType[view.label].alt = "Minecraft " + view.label + " Map"; + MCMapType[view.label].projection = new MCMapProjection(); + + if (view.overlay) { + overlayMapTypes.push(MCMapType[view.label]); + } else { + if (mapTypeIdDefault == null) { + mapTypeIdDefault = 'mcmap' + view.label; + } + mapTypeIds.push('mcmap' + view.label); } - mapTypeIds.push('mcmap' + view.label); } - function CoordMapType() { - } +function CoordMapType() { +} - function CoordMapType(tileSize) { +function CoordMapType(tileSize) { this.tileSize = tileSize; - } +} - CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { +CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { var div = ownerDocument.createElement('DIV'); div.innerHTML = "(" + coord.x + ", " + coord.y + ", " + zoom + ")"; div.innerHTML += "
"; @@ -549,4 +691,4 @@ for (idx in mapTypeData) { div.style.borderWidth = '1px'; div.style.borderColor = '#AAAAAA'; return div; - }; +}; diff --git a/web_assets/style.css b/web_assets/style.css index e6029dc..8585eb3 100644 --- a/web_assets/style.css +++ b/web_assets/style.css @@ -17,13 +17,13 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } font-family: monospace; } -#signControl { +#customControl { padding: 5px; height: 15px; font-family: Arial, sans-serif; } -#signControl > div#top { +#customControl > div#top { background-color: #fff; border: 2px solid #000; text-align: center; @@ -33,7 +33,14 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } cursor: pointer; } -#signControl > div#dropDown { +#customControl > div#dropDown { + border: 1px solid #000; + font-size: 12px; + background-color: #fff; + display: none; +} + +#customControl > div#button { border: 1px solid #000; font-size: 12px; background-color: #fff;