Mercurial > hg > forks > yadex
view src/drawmap.cc @ 107:20aa5a515896
Reformat one line /* */ comments to //
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 06 Oct 2014 12:42:55 +0300 |
parents | 2f1ecc1c5f72 |
children | f05330267c66 |
line wrap: on
line source
/* * drawmap.cc * AYM 1998-09-06 */ /* This file is part of Yadex. Yadex incorporates code from DEU 5.21 that was put in the public domain in 1994 by Raphaël Quinet and Brendon Wyber. The rest of Yadex is Copyright © 1997-2003 André Majorel and others. This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include "yadex.h" #include <math.h> #include <algorithm> #include <map> #include <vector> #ifdef Y_X11 #include <X11/Xlib.h> #endif #include "_edit.h" #include "disppic.h" // Sprites #include "drawmap.h" #include "game.h" // Sprites #include "gfx.h" #include "imgscale.h" #include "imgspect.h" #include "levels.h" #include "lists.h" #include "pic2img.h" #include "s_centre.h" #include "sticker.h" #include "things.h" #include "vectext.h" #include "wadres.h" static void draw_grid(edit_t * e); static void draw_vertices(edit_t * e); static void draw_linedefs(edit_t * e); static void draw_things_squares(edit_t * e); static void draw_things_sprites(edit_t * e); static void draw_obj_no(int x, int y, int obj_no, acolour_t c); /* * vertex_radius - apparent radius of a vertex, in pixels * * Try this in Gnuplot : * * plot [0:10] x * replot log(x+1.46)/log(1.5)-log(2.46)/log(1.5)+1 */ int vertex_radius(double scale) { #if 0 static double last_scale = 0; static int last_result = 0; if (scale == last_scale) return last_result; const int VERTEX_PIXELS = 5; // The scale past which we switch from linear to logarithmic. const double crossover = 0.1; // The base of the log. The higher, the stronger the effect. const double base = 1.4; /* The point at which the derivative of log{base}(x) is 1. This is where we want the crossover to occur. */ const double knee_x = 1 / log(base); const double knee_y = log(knee_x) / log(base); double factor; if (scale <= crossover) factor = scale; else factor = crossover + log(scale - crossover + knee_x) / log(base) - knee_y; last_result = (int) (VERTEX_PIXELS * factor + 0.5); return last_result; #else const int VERTEX_PIXELS = 6; return (int) (VERTEX_PIXELS * (0.2 + scale / 2)); #endif } /* draw the actual game map */ void draw_map(edit_t * e) // SWAP! { int mapx0 = MAPX(0); int mapx9 = MAPX(cfg.ScrMaxX); int mapy0 = MAPY(cfg.ScrMaxY); int mapy9 = MAPY(0); int n; // Draw the grid first since it's in the background draw_grid(e); if (e->global) { draw_linedefs(e); if (e->show_things_sprites) draw_things_sprites(e); else draw_things_squares(e); draw_vertices(e); } else { if (e->obj_type != OBJ_THINGS) draw_things_squares(e); draw_linedefs(e); if (e->obj_type == OBJ_VERTICES) draw_vertices(e); if (e->obj_type == OBJ_THINGS) { if (e->show_things_sprites) draw_things_sprites(e); else draw_things_squares(e); } } // Draw the things numbers if (e->obj_type == OBJ_THINGS && e->show_object_numbers) { for (n = 0; n < NumThings; n++) { int mapx = Things[n].xpos; int mapy = Things[n].ypos; if (mapx < mapx0 || mapx > mapx9 || mapy < mapy0 || mapy > mapy9) continue; draw_obj_no(SCREENX(mapx) + FONTW, SCREENY(mapy) + 2, n, THING_NO); } } // Draw the sector numbers if (e->obj_type == OBJ_SECTORS && e->show_object_numbers) { int xoffset = -FONTW / 2; for (n = 0; n < NumSectors; n++) { int mapx; int mapy; centre_of_sector(n, &mapx, &mapy); if (mapx >= mapx0 && mapx <= mapx9 && mapy >= mapy0 && mapy <= mapy9) draw_obj_no(SCREENX(mapx) + xoffset, SCREENY(mapy) - FONTH / 2, n, SECTOR_NO); if (n == 10 || n == 100 || n == 1000 || n == 10000) xoffset -= FONTW / 2; } } } /* * draw_grid - draw the grid in the background of the edit window */ static void draw_grid(edit_t * e) { if (!e->grid_shown) return; int mapx0 = MAPX(0); int mapx1 = MAPX(cfg.ScrMaxX); int mapy0 = MAPY(cfg.ScrMaxY); int mapy1 = MAPY(0); int grid_step_1 = e->grid_step; // Map units between dots int grid_step_2 = 4 * grid_step_1; // Map units between dim lines int grid_step_3 = 4 * grid_step_2; // Map units between bright lines int grid_step_4 = 4 * grid_step_3; // Map units between brighter lines { set_colour(GRID2V); int mapx0_2 = (mapx0 / grid_step_2) * grid_step_2; if (mapx0_2 < mapx0) mapx0_2 += grid_step_2; for (int i = mapx0_2; i <= mapx1; i += grid_step_2) if (i % grid_step_3 != 0) DrawMapLine(i, mapy0, i, mapy1); } { set_colour(GRID2H); int mapy0_2 = (mapy0 / grid_step_2) * grid_step_2; if (mapy0_2 < mapy0) mapy0_2 += grid_step_2; for (int j = mapy0_2; j <= mapy1; j += grid_step_2) if (j % grid_step_3 != 0) DrawMapLine(mapx0, j, mapx1, j); } { set_colour(GRID3V); int mapx0_3 = (mapx0 / grid_step_3) * grid_step_3; if (mapx0_3 < mapx0) mapx0_3 += grid_step_3; for (int i = mapx0_3; i <= mapx1; i += grid_step_3) if (i % grid_step_4 != 0) DrawMapLine(i, mapy0, i, mapy1); } { set_colour(GRID3H); int mapy0_3 = (mapy0 / grid_step_3) * grid_step_3; if (mapy0_3 < mapy0) mapy0_3 += grid_step_3; for (int j = mapy0_3; j <= mapy1; j += grid_step_3) if (j % grid_step_4 != 0) DrawMapLine(mapx0, j, mapx1, j); } { set_colour(GRID4V); int mapx0_4 = (mapx0 / grid_step_4) * grid_step_4; if (mapx0_4 < mapx0) mapx0_4 += grid_step_4; //printf ("MAPX(0): %d mapx0_4: %d\n", MAPX(0), mapx0_4); // DEBUG for (int i = mapx0_4; i <= mapx1; i += grid_step_4) DrawMapLine(i, mapy0, i, mapy1); } { set_colour(GRID4H); int mapy0_4 = (mapy0 / grid_step_4) * grid_step_4; if (mapy0_4 < mapy0) mapy0_4 += grid_step_4; for (int j = mapy0_4; j <= mapy1; j += grid_step_4) DrawMapLine(mapx0, j, mapx1, j); } { int mapx0_1 = (mapx0 / grid_step_1) * grid_step_1; if (mapx0_1 < mapx0) mapx0_1 += grid_step_1; int mapy0_1 = (mapy0 / grid_step_1) * grid_step_1; if (mapy0_1 < mapy0) mapy0_1 += grid_step_1; #ifdef Y_X11 // Optimisation for X: draw several points in one go int npoints = (mapx1 - mapx0_1) / grid_step_1 + 1; XPoint *points = (XPoint *) malloc(npoints * sizeof *points); points[0].x = SCREENX(mapx0_1); int n = 1; int last_i = points[0].x; for (int i = mapx0_1 + grid_step_1; i <= mapx1; i += grid_step_1) { if (n >= npoints) nf_bug("%d >= %d", n, npoints); points[n].x = SCREENX(i) - last_i; points[n].y = 0; n++; last_i = SCREENX(i); } npoints = n; set_colour(GRID1); for (int j = mapy0_1; j <= mapy1; j += grid_step_1) { points[0].y = SCREENY(j); XDrawPoints(dpy, drw, gc, points, npoints, CoordModePrevious); } free(points); #else // Generic code. Untested. int npoints = (mapx1 - mapx0_1) / grid_step_1 + 1; int dispx[npoints]; for (int n = 0; n < npoints; n++) dispx[n] = SCREENX(mapx0_1 + n * grid_step_1); for (int j = mapy0_1; j <= mapy1; j += grid_step_1) { int dispy = SCREENY(j); for (int n = 0; n < npoints; n++) { draw_point(dispx[n], dispy); } } #endif } } /* * draw_vertices - draw the vertices, and possibly their numbers */ static void draw_vertices(edit_t * e) { int mapx0 = MAPX(0); int mapx9 = MAPX(cfg.ScrMaxX); int mapy0 = MAPY(cfg.ScrMaxY); int mapy9 = MAPY(0); const int r = vertex_radius(cfg.Scale); push_colour(LIGHTGREEN); for (int n = 0; n < NumVertices; n++) { int mapx = Vertices[n].x; int mapy = Vertices[n].y; if (mapx >= mapx0 && mapx <= mapx9 && mapy >= mapy0 && mapy <= mapy9) { int scrx = SCREENX(mapx); int scry = SCREENY(mapy); DrawScreenLine(scrx - r, scry - r, scrx + r, scry + r); DrawScreenLine(scrx + r, scry - r, scrx - r, scry + r); } } if (e->show_object_numbers) { for (int n = 0; n < NumVertices; n++) { int mapx = Vertices[n].x; int mapy = Vertices[n].y; if (mapx >= mapx0 && mapx <= mapx9 && mapy >= mapy0 && mapy <= mapy9) { int x = (int) (SCREENX(mapx) + 2 * r); int y = SCREENY(mapy) + 2; draw_obj_no(x, y, n, VERTEX_NO); } } } pop_colour(); } /* * draw_linedefs - draw the linedefs */ static int ld_check(VPtr v1, VPtr v2) { const int mapx0 = MAPX(0), mapx9 = MAPX(cfg.ScrMaxX), mapy0 = MAPY(cfg.ScrMaxY), mapy9 = MAPY(0); return ((v1->x < mapx0 && v2->x < mapx0) || (v1->x > mapx9 && v2->x > mapx9) || (v1->y < mapy0 && v2->y < mapy0) || (v1->y > mapy9 && v2->y > mapy9)); } static void draw_linedefs(edit_t * e) { int new_colour, current_colour = INT_MIN; // Some impossible colour no. switch (e->obj_type) { case OBJ_THINGS: { int new_colour, current_colour = INT_MIN; // Some impossible colour no. for (int n = 0; n < NumLineDefs; n++) { VPtr v1 = &Vertices[LineDefs[n].start], v2 = &Vertices[LineDefs[n].end]; if (ld_check(v1, v2)) continue; new_colour = (LineDefs[n].flags & 1) ? WHITE : LIGHTGREY; if (new_colour != current_colour) set_colour(current_colour = new_colour); DrawMapLine(v1->x, v1->y, v2->x, v2->y); } break; } case OBJ_VERTICES: set_colour(LIGHTGREY); for (int n = 0; n < NumLineDefs; n++) { VPtr v1 = &Vertices[LineDefs[n].start], v2 = &Vertices[LineDefs[n].end]; if (ld_check(v1, v2)) continue; DrawMapVector(v1->x, v1->y, v2->x, v2->y); } break; case OBJ_LINEDEFS: for (int n = 0; n < NumLineDefs; n++) { VPtr v1 = &Vertices[LineDefs[n].start], v2 = &Vertices[LineDefs[n].end]; if (ld_check(v1, v2)) continue; if (LineDefs[n].type != 0) // AYM 19980207: was "> 0" new_colour = (LineDefs[n].tag != 0) ? LIGHTMAGENTA : LIGHTGREEN; else new_colour = (LineDefs[n].flags & 1) ? WHITE : LIGHTGREY; // Signal errors by drawing the linedef in red. Needs work. // Tag on a typeless linedef if (LineDefs[n].type == 0 && LineDefs[n].tag != 0) new_colour = LIGHTRED; // No first sidedef if (!is_sidedef(LineDefs[n].sidedef1)) new_colour = LIGHTRED; // Bad second sidedef if (!is_sidedef(LineDefs[n].sidedef2) && LineDefs[n].sidedef2 != -1) new_colour = LIGHTRED; if (new_colour != current_colour) set_colour(current_colour = new_colour); DrawMapLine(v1->x, v1->y, v2->x, v2->y); if (e->show_object_numbers) { int scnx0 = SCREENX(v1->x); int scnx1 = SCREENX(v2->x); int scny0 = SCREENY(v1->y); int scny1 = SCREENY(v2->y); int label_width = ((int) log10(n) + 1) * FONTW; if (abs(scnx1 - scnx0) > label_width + 4 || abs(scny1 - scny0) > label_width + 4) { int scnx = (scnx0 + scnx1) / 2 - label_width / 2; int scny = (scny0 + scny1) / 2 - FONTH / 2; draw_obj_no(scnx, scny, n, LINEDEF_NO); } } } break; case OBJ_SECTORS: int current_colour = INT_MIN; // Some impossible colour no. int new_colour; for (int n = 0; n < NumLineDefs; n++) { VPtr v1 = &Vertices[LineDefs[n].start], v2 = &Vertices[LineDefs[n].end]; if (ld_check(v1, v2)) continue; int sd1 = OBJ_NO_NONE; int sd2 = OBJ_NO_NONE; int s1 = OBJ_NO_NONE; int s2 = OBJ_NO_NONE; // FIXME should flag negative sidedef numbers as errors // FIXME should flag unused tag as errors if ((sd1 = LineDefs[n].sidedef1) < 0 || sd1 >= NumSideDefs|| (s1 = SideDefs[sd1].sector) < 0 || s1 >= NumSectors || (sd2 = LineDefs[n].sidedef2) >= NumSideDefs || (sd2 >= 0 && ((s2 = SideDefs[sd2].sector) < 0 || s2 >= NumSectors))) { new_colour = LIGHTRED; } else { bool have_tag = false, have_type = false; if (Sectors[s1].tag != 0) have_tag = true; if (Sectors[s1].special != 0) have_type = true; if (sd2 >= 0) { if (Sectors[s2].tag != 0) have_tag = true; if (Sectors[s2].special != 0) have_type = true; } if (have_tag && have_type) new_colour = SECTOR_TAGTYPE; else if (have_tag) new_colour = SECTOR_TAG; else if (have_type) new_colour = SECTOR_TYPE; else new_colour = (LineDefs[n].flags & 1) ? WHITE : LIGHTGREY; } if (new_colour != current_colour) set_colour(current_colour = new_colour); DrawMapLine(v1->x, v1->y, v2->x, v2->y); } break; } } /* * draw_things_squares - the obvious */ static void draw_things_squares(edit_t * e) { // The radius of the largest thing. int max_radius = get_max_thing_radius(); static const short xsign[] = { 1, 1, 0, -1, -1, -1, 0, 1, 0 }; static const short ysign[] = { 0, 1, 1, 1, 0, -1, -1, -1, 0 }; /* A thing is guaranteed to be totally off-screen if its centre is more than <max_radius> units beyond the edge of the screen. */ int mapx0 = MAPX(0) - max_radius; int mapx9 = MAPX(cfg.ScrMaxX) + max_radius; int mapy0 = MAPY(cfg.ScrMaxY) - max_radius; int mapy9 = MAPY(0) + max_radius; push_colour(THING_REM); for (int n = 0; n < NumThings; n++) { const int mapx = Things[n].xpos, mapy = Things[n].ypos; if (mapx < mapx0 || mapx > mapx9 || mapy < mapy0 || mapy > mapy9) continue; const int m = get_thing_radius(Things[n].type), direction = angle_to_direction(Things[n].angle); if (e->obj_type == OBJ_THINGS) set_colour(get_thing_colour(Things[n].type)); DrawMapLine(mapx - m, mapy - m, mapx + m, mapy - m); DrawMapLine(mapx + m, mapy - m, mapx + m, mapy + m); DrawMapLine(mapx + m, mapy + m, mapx - m, mapy + m); DrawMapLine(mapx - m, mapy + m, mapx - m, mapy - m); DrawMapLine(mapx, mapy, mapx + (m * xsign[direction]), mapy + (m * ysign[direction])); } pop_colour(); } /* Drawing the things sprites is done here. To avoid having large sprites obscure small ones, we display the large sprites first and the small ones last. To do that, we maintain a list of all things in the level, sorted in descending number of opaque pixels in the sprite. Actually, we approximate that by the size of the lump. Usually, the size of the lump is roughly monotonic w.r.t. the number of opaque pixels. And it's much simpler and faster than counting the pixels. That list serves a second purpose : optimization. Because of the way it's sorted, two things that have the same graphic representation are always contiguous in the list. That property allows us to save quite a few calls to LoadPicture(), scale_img(), spectrify_img() and Sticker::load(). Given that those are very expensive operations and that the average level contains many repetitions of certain things (E.G. former humans), the benefit is considerable. On certain semi-pathological levels like Robin Holden's court30.wad, it makes display several times faster. We approximate "two things have the same graphic represention" by "two things have the same type". Strictly speaking, it's not the same thing. Two distinct thing types could very well have the same graphic representation. In fact it does happen (cf. things 49 and 63). However, since the alternative is to sort based on the quintuplet (wad, lump offset, flags, dye), we're better off this way. */ class Thing_npixels { public: Thing_npixels(i16 thing_no, unsigned long npixels, wad_ttype_t type):thing_no(thing_no), npixels(npixels), type(type) { } bool operator<(const Thing_npixels & other) const { return ((this->npixels > other.npixels || this->npixels == other.npixels) && this->type < other.type); } i16 thing_no; unsigned long npixels; wad_ttype_t type; }; class Thing_list_by_size { public: Thing_list_by_size() { } ~Thing_list_by_size() { } const Thing_npixels & operator[] (int n) { return a[n]; } void refresh() { a.clear(); a.reserve(NumThings); for (int n = 0; n < NumThings; n++) { Lump_loc loc; const char *sprite_root = get_thing_sprite(Things[n].type); if (sprite_root != NULL) wad_res.sprites.loc_by_root(sprite_root, loc); else loc.len = 0; a.push_back(Thing_npixels((i16) n, loc.len, Things[n].type)); } sort(a.begin(), a.end()); } private: std::vector < Thing_npixels > a; }; static Thing_list_by_size list; //static unsigned long things_angles_prev; // Unused for now static unsigned long things_types_prev; /* This map is used to cache widths and heights. We need them to skip off-screen sprites. */ struct sprite_dim_t { sprite_dim_t() { } sprite_dim_t(int width, int height):width(width), height(height) { } unsigned short width; unsigned short height; }; typedef std::map < i16, sprite_dim_t > dim_map_t; static dim_map_t dim_map; // FIXME there should be one for each game /* * draw_things_sprites - the obvious */ static void draw_things_sprites(edit_t * e) { #ifdef NO_RENDER static #endif Sticker sticker; wad_ttype_t last_type = -1; // Type of last thing displayed dim_map_t::iterator dim = dim_map.end(); bool set_dim = true; // Init to avoid warning const unsigned short max_width = 1000; const unsigned short max_height = 1000; int mapx0 = 0; int mapx9 = 0; int mapy0 = 0; int mapy9 = 0; if (things_types_prev != things_types) { list.refresh(); things_types_prev = things_types; } #ifdef NO_RENDER static double last_scale = 0; if (last_scale != cfg.Scale) { Lump_loc loc; wad_res.sprites.loc_by_root("PLAY", loc); Img img; LoadPicture(img, "PLAYA0", loc, 0, 0); Img img_scaled; scale_img(img, cfg.Scale * cfg.sprite_scale / 100, img_scaled); sprite.load(img_scaled, false); last_scale = cfg.Scale; } #endif push_colour(CYAN); for (int n = 0; n < NumThings; n++) { const Thing_npixels & t = list[n]; // Skip off-screen things if (t.type != last_type) { dim = dim_map.find(t.type); if (dim == dim_map.end()) { set_dim = true; mapx0 = MAPX(0) - max_width / 2; mapx9 = MAPX(cfg.ScrMaxX) + max_width / 2; mapy0 = MAPY(cfg.ScrMaxY) - max_height / 2; mapy9 = MAPY(0) + max_height / 2; } else { mapx0 = MAPX(0) - dim->second.width / 2; mapx9 = MAPX(cfg.ScrMaxX) + dim->second.width / 2; mapy0 = MAPY(cfg.ScrMaxY) - dim->second.height / 2; mapy9 = MAPY(0) + dim->second.height / 2; } } int mapx = Things[t.thing_no].xpos; int mapy = Things[t.thing_no].ypos; if (mapx < mapx0 || mapx > mapx9 || mapy < mapy0 || mapy > mapy9) continue; #ifndef NO_RENDER // If not the same as the last thing displayed, rasterize it if (t.type != last_type) { last_type = t.type; const char *sprite_root = get_thing_sprite(t.type); if (sprite_root != NULL) { Lump_loc loc; wad_res.sprites.loc_by_root(sprite_root, loc); Img img_raw, img_scaled; if (LoadPicture(img_raw, sprite_root, loc, 0, 0)) { sticker.clear(); // We'll display the thing type instead } else { if (set_dim) { dim_map[t.type] = sprite_dim_t(img_raw.width(), img_raw.height()); set_dim = false; } scale_img(img_raw, cfg.Scale * cfg.sprite_scale / 100, img_scaled); if (get_thing_flags(t.type) & THINGDEF_SPECTRAL) spectrify_img(img_scaled); sticker.load(img_scaled, false); } } else sticker.clear(); // We'll display the thing type instead } #endif // Display it if (sticker.is_clear()) draw_vint(t.type, SCREENX(mapx), SCREENY(mapy), cfg.Scale); else sticker.draw(drw, 'c', SCREENX(mapx), SCREENY(mapy)); } pop_colour(); } /* * draw_obj_no - draw a number at screen coordinates (x, y) * * FIXME too slow. */ static void draw_obj_no(int x, int y, int obj_no, acolour_t c) { push_colour(BLACK); #if 1 DrawScreenText(x - 2, y, "%d", obj_no); DrawScreenText(x - 1, y, "%d", obj_no); DrawScreenText(x + 1, y, "%d", obj_no); DrawScreenText(x + 2, y, "%d", obj_no); DrawScreenText(x, y + 1, "%d", obj_no); DrawScreenText(x, y - 1, "%d", obj_no); #else DrawScreenText(x + 1, y + 1, "%d", obj_no); DrawScreenText(x + 1, y - 1, "%d", obj_no); DrawScreenText(x - 1, y + 1, "%d", obj_no); DrawScreenText(x - 1, y - 1, "%d", obj_no); #endif set_colour(c); DrawScreenText(x, y, "%d", obj_no); pop_colour(); }