[PL::HTMLMap::] Render HTML Map. To render the spatial map of rooms as HTML. @h Building the grids. Three three-dimensional arrays called "grids" are used to store a rasterised version of the map before we render this on screen. The |room_grid| tells us which room can be found at $(x, y, z)$, while the |icon_grid| is 25 times larger since it splits each room cell into a 5 by 5 subgrid of icons. Bitmaps stored in the 16 icon cells around the perimeter of the 5 by 5 subgrid tell us which exits to mark (and since we map only 12 kinds of exit, this means that four of them are unused). The central 3 by 3 part of the subgrid is not used: nothing can be plotted there since that's where the room icon goes, which is made not with an image tag but using the HTML table routine below. We will often use the wasteful coordinate system $(x, y, z, i_1, i_2)$ to mean the icon at $(i_1, i_2)$ (with $0\leq i_1, i_2\leq 4$) associated with the room cell at $(x, y, z)$. The |exit_grid| stores which direction number is the exit being marked at this icon position, and has the same indexing as the icon grid. @d ROOM_GRID_POS(P) Geometry::cuboid_index(P, Universe) @d ICON_GRID_POS(P, i1, i2) (25*ROOM_GRID_POS(P) + 5*(i1) + (i2)) = faux_instance **room_grid = NULL; int *icon_grid = NULL, *exit_grid = NULL; void PL::HTMLMap::calculate_map_grid(void) { faux_instance_set *faux_set = InterpretIndex::get_faux_instances(); @; @; @; @; } @ = int size_needed = Geometry::cuboid_volume(Universe), x; room_grid = (faux_instance **) (Memory::calloc(size_needed, sizeof(faux_instance *), MAP_INDEX_MREASON)); for (x=0; x = faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) room_grid[ROOM_GRID_POS(Room_position(R))] = R; @ = faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) { int exit; LOOP_OVER_STORY_DIRECTIONS(exit) if (PL::SpatialMap::direction_is_mappable(exit)) { faux_instance *D = NULL; /* door which the exit passes through, if it does */ faux_instance *T = PL::SpatialMap::room_exit(R, exit, &D); /* target at the other end */ if ((T) || (D)) @; } } @ We next define constants needed for the icon bitmap. The information we extract from the map exits is recorded in the low four bits as follows: @d EXIT_MAPBIT 0x00000001 /* An exit leads this way */ @d DOOR1_MAPBIT 0x00000002 /* Into a 1-sided door */ @d DOOR2_MAPBIT 0x00000004 /* Into a 2-sided door */ @d CONNECTIVE_BITMAP (EXIT_MAPBIT+DOOR1_MAPBIT+DOOR2_MAPBIT) @ The higher bits are used for the nuances which improve the map when several rooms are plotted together. @d ADJACENT_MAPBIT 0x00000008 /* Into the room adjacent in space */ @d ALIGNED_MAPBIT 0x00000010 /* Into a room in correct direction */ @d FADING_MAPBIT 0x00000020 /* There's a broken exit on ... */ @d MEET_MAPBIT 0x00000040 /* This door should meet the adjacent one */ @d CROSSDOOR_MAPBIT 0x00000080 /* There's a door on the diagonal athwart */ @d CROSSDOT_MAPBIT 0x00000100 /* There's a plain exit on ... */ @ Five bits are used for the possible contents of a central square: it may be occupied by an actual room, or it may have a pile of long straight-line connections running through it, in any combination. @d LONGEW_MAPBIT 0x00000200 @d LONGNS_MAPBIT 0x00000400 @d LONGSWNE_MAPBIT 0x00000800 @d LONGNWSE_MAPBIT 0x00001000 @d OCCUPIED_MAPBIT 0x10000000 @d LONGS_BITMAP (LONGEW_MAPBIT+LONGNS_MAPBIT+LONGSWNE_MAPBIT+LONGNWSE_MAPBIT) @ The following code calculates the low four bits of the icon bitmap grid. Note that the main map grid must already be finished before this stage can even begin. @ = int i1, i2; PL::SpatialMap::cell_position_for_direction(exit, &i1, &i2); int bitmap = 0; if (D) { if (T) bitmap |= DOOR2_MAPBIT; else bitmap |= DOOR1_MAPBIT; } if (T) { bitmap |= EXIT_MAPBIT; vector E = PL::SpatialMap::direction_as_vector(exit); if ((Geometry::vec_eq(E, Zero_vector) == FALSE) && (PL::SpatialMap::direction_is_lateral(exit))) { @; @; } } icon_grid[ICON_GRID_POS(Room_position(R), i1, i2)] = bitmap; exit_grid[ICON_GRID_POS(Room_position(R), i1, i2)] = exit; @ Suppose we are looking east from the Ballroom to the Kitchens. If the Kitchens will indeed be plotted at the position directly east of the Ballroom, we award the "adjacent" bit; if they will be plotted due east, but further away than a single square distant, then we get the "aligned" bit as a consolation prize. @ = vector V = Geometry::vec_minus(Room_position(T), Room_position(R)); int lambda; for (lambda=1; lambda<10; lambda++) if (Geometry::vec_eq(V, Geometry::vec_scale(lambda, E))) { if (lambda == 1) bitmap |= ADJACENT_MAPBIT; else bitmap |= ALIGNED_MAPBIT; } @ If a different room altogether -- say, the Tack Room -- is being plotted one square east of the Ballroom, even though the map connection leads to the Kitchens, then we get the "fading" bit. (At one time connections like this were going to be plotted in a sort of fading-away grey gradient, hence the name, but in the end a more cartoonish break looked better.) This is not exclusive with the aligned bit: we might have a situation where the map be will be plotted left-to-right as B, TR, K, even though the connection is east from B to K. If so, we get both the fading and aligned bits. @ = vector Farend = Geometry::vec_plus(Room_position(R), E); faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) if ((R != T) && (Geometry::vec_eq(Room_position(R), Farend))) bitmap |= FADING_MAPBIT; @ = faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) icon_grid[ICON_GRID_POS(Room_position(R), 2, 2)] = OCCUPIED_MAPBIT; LOOP_OVER_FAUX_ROOMS(faux_set, R) { vector P = Room_position(R); PL::HTMLMap::correct_pair(P, SW_vector, 0, 4, 4, 0); PL::HTMLMap::correct_pair(P, W_vector, 0, 2, 4, 2); PL::HTMLMap::correct_pair(P, NW_vector, 0, 0, 4, 4); PL::HTMLMap::correct_pair(P, S_vector, 2, 4, 2, 0); PL::HTMLMap::correct_pair(P, N_vector, 2, 0, 2, 4); PL::HTMLMap::correct_pair(P, SE_vector, 4, 4, 0, 0); PL::HTMLMap::correct_pair(P, E_vector, 4, 2, 0, 2); PL::HTMLMap::correct_pair(P, NE_vector, 4, 0, 0, 4); } @ A process called "pair correction" fills in the nuance bits for all adjacent icons representing the same exit. Thus the east side icon of one room may need to be married up with the west side icon of the adjacent room, and so on. The four by four cornices diagonally in between rooms require special care. To plot a northeast exit blocked by a 2-sided door, for faux_instance, requires all four icons to be plotted, but we need to be careful in case the two icons not occupied by the exit are needed for something else (if a northwest exit crossed over it, for faux_instance). Here $P$ is the position of the room we're looking at, and $D$ an offset vector to one of its eight neighbouring cell positions on the map. If $P+D$ lies outside the map altogether, we do nothing. = void PL::HTMLMap::correct_pair(vector P, vector D, int from_i1, int from_i2, int to_i1, int to_i2) { vector Q = Geometry::vec_plus(P, D); if (Geometry::within_cuboid(P, Universe) == FALSE) return; /* should never happen */ if (Geometry::within_cuboid(Q, Universe) == FALSE) return; /* neighbouring cell outside map */ int from = icon_grid[ICON_GRID_POS(P, from_i1, from_i2)]; int to = icon_grid[ICON_GRID_POS(Q, to_i1, to_i2)]; if ((D.x == 0) || (D.y == 0)) @ else @; if (from & ALIGNED_MAPBIT) @; } @ Let's see how the "long" bits are added first, since that's easier. The following looks disturbingly like an infinite loop: it lays out the roadway, one cell at a time (adding the direction vector $D$ to our position $P$ each turn) but stops when it hits an occupied cell -- one with a room plotted in it. This must eventually happen because the exit is "aligned", which means that it leads to a room whose position is some multiple of $D$ offset from the original. So the loop always terminates. @ = while (TRUE) { P = Geometry::vec_plus(P, D); if (icon_grid[ICON_GRID_POS(P, 2, 2)] & OCCUPIED_MAPBIT) break; if ((Geometry::vec_eq(D, E_vector)) || (Geometry::vec_eq(D, W_vector))) { icon_grid[ICON_GRID_POS(P, 0, 2)] = EXIT_MAPBIT; icon_grid[ICON_GRID_POS(P, 2, 2)] |= LONGEW_MAPBIT; icon_grid[ICON_GRID_POS(P, 4, 2)] = EXIT_MAPBIT; } else if ((Geometry::vec_eq(D, N_vector)) || (Geometry::vec_eq(D, S_vector))) { icon_grid[ICON_GRID_POS(P, 2, 0)] = EXIT_MAPBIT; icon_grid[ICON_GRID_POS(P, 2, 2)] |= LONGNS_MAPBIT; icon_grid[ICON_GRID_POS(P, 2, 4)] = EXIT_MAPBIT; } else if ((Geometry::vec_eq(D, SW_vector)) || (Geometry::vec_eq(D, NE_vector))) { icon_grid[ICON_GRID_POS(P, 0, 4)] = EXIT_MAPBIT; icon_grid[ICON_GRID_POS(P, 2, 2)] |= LONGSWNE_MAPBIT; icon_grid[ICON_GRID_POS(P, 4, 0)] = EXIT_MAPBIT; } else if ((Geometry::vec_eq(D, NW_vector)) || (Geometry::vec_eq(D, SE_vector))) { icon_grid[ICON_GRID_POS(P, 0, 0)] = EXIT_MAPBIT; icon_grid[ICON_GRID_POS(P, 2, 2)] |= LONGNWSE_MAPBIT; icon_grid[ICON_GRID_POS(P, 4, 4)] = EXIT_MAPBIT; } } @ That leaves just three bits left to set: meet, crossdoor and crossdot. The meet bit is used to show that the door on a connection should be plotted symmetrically between the two rooms it connects. This is easy for the directions N, S, E and W: @ = if ((from == to) && (from & ADJACENT_MAPBIT) && (from & DOOR2_MAPBIT)) { icon_grid[ICON_GRID_POS(P, from_i1, from_i2)] |= MEET_MAPBIT; icon_grid[ICON_GRID_POS(Q, to_i1, to_i2)] |= MEET_MAPBIT; } @ But the case of a diagonal direction is much harder, because we may need to add cornice-pieces. There are four possible diagonal directions (NE, NW, SE and SW), and in each case the origin $P$ and the neighbour cell $P+D$ must live in opposite corners of a $2\times 2$ box of cells. We set $N$ to the bottom left cell of this box (which might be one of the two cells we were originally looking at, or might be one of the other two). @ = vector N = P; if (D.x < 0) N.x--; if (D.y < 0) N.y--; PL::HTMLMap::correct_diagonal(N, TRUE); PL::HTMLMap::correct_diagonal(N, FALSE); @ So now the vector $BL$ represents the bottom left cell (i.e., the southwestern corner of the box). We can obtain the other three cells of the $2\times 2$ box by offsetting to N, E and NE. Two of these cells form the diagonal of the map connection (are "used"), and two are off-diagonal (are "unused"). = void PL::HTMLMap::correct_diagonal(vector BL, int SW_to_NE) { int pos_00, /* corner icon position of lower cell used by the map connection */ pos_01, /* corner icon position of lower cell not used by the map connection */ pos_10, /* corner icon position of upper cell not used by the map connection */ pos_11; /* corner icon position of upper cell used by the map connection */ if (SW_to_NE) { pos_00 = ICON_GRID_POS(BL, 4, 0); pos_01 = ICON_GRID_POS(Geometry::vec_plus(BL, N_vector), 4, 4); pos_10 = ICON_GRID_POS(Geometry::vec_plus(BL, E_vector), 0, 0); pos_11 = ICON_GRID_POS(Geometry::vec_plus(BL, NE_vector), 0, 4); } else { pos_00 = ICON_GRID_POS(Geometry::vec_plus(BL, E_vector), 0, 0); pos_01 = ICON_GRID_POS(Geometry::vec_plus(BL, NE_vector), 0, 4); pos_10 = ICON_GRID_POS(BL, 4, 0); pos_11 = ICON_GRID_POS(Geometry::vec_plus(BL, N_vector), 4, 4); } @; @; } @ = int from = icon_grid[pos_00], to = icon_grid[pos_11]; if (from == to) { if ((from & ADJACENT_MAPBIT) && (from & DOOR2_MAPBIT) && ((from & MEET_MAPBIT) == 0)) { if ((icon_grid[pos_01] == 0) && (icon_grid[pos_10] == 0)) @ else @; } } @ If the off-diagonal cells happen to be free, we can use them to draw a nice large door which is exactly halfway between the two rooms it connects, and is perpendicular to the direction of the map connection. @ = icon_grid[pos_00] |= MEET_MAPBIT; icon_grid[pos_11] |= MEET_MAPBIT; icon_grid[pos_01] = CROSSDOOR_MAPBIT; icon_grid[pos_10] = CROSSDOOR_MAPBIT; @ But if the off-diagonal cells aren't free, we have no room for that, and must draw a much smaller door on one end or the other (not both) of the connection. @ = icon_grid[pos_00] = DOOR2_MAPBIT; /* mark the door on the BL half of the exit */ icon_grid[pos_11] = EXIT_MAPBIT; /* with no door on the other half of the exit */ @ If the off-diagonal cells happen to be free, we can put little single-pixel icons into them to repair the notches which would otherwise show when the map connection stripe (3 pixels wide) passes through a cell corner. @ = int from = icon_grid[pos_00], to = icon_grid[pos_11]; if ((from == to) && (from & CONNECTIVE_BITMAP) && (icon_grid[pos_01] == 0) && (icon_grid[pos_10] == 0)) { icon_grid[pos_01] = CROSSDOT_MAPBIT; icon_grid[pos_10] = CROSSDOT_MAPBIT; } @h Nested HTML Tables. In 2010 it is considered something of a heresy to be still doing web page layout using nested tables - supposedly, CSS is now strong enough for all our needs - but the map is unusually well suited to a table approach since it consists, in the end, of tessalations of rectangles. Here's the code we will use to create each HTML table. = int map_tables_begun = 2; void PL::HTMLMap::begin_variable_width_table(OUTPUT_STREAM) { @; map_tables_begun++; HTML::begin_html_table(OUT, NULL, FALSE, 0, 0, 0, 0, 0); } void PL::HTMLMap::begin_map_table(OUTPUT_STREAM, int width, int height) { @; map_tables_begun++; HTML::begin_html_table(OUT, NULL, FALSE, 0, 0, 0, height, width); } void PL::HTMLMap::begin_variable_width_table_with_background(OUTPUT_STREAM, char *bg_image) { @; map_tables_begun++; HTML::begin_html_table_bg(OUT, NULL, FALSE, 0, 0, 0, 0, 0, bg_image); } @ Each table, however begun, concludes with: = void PL::HTMLMap::end_map_table(OUTPUT_STREAM) { map_tables_begun--; @; HTML::end_html_table(OUT); WRITE("\n"); } @ = WRITE("\n"); for (int i=0; i; @; if (FauxInstances::no_rooms() >= 2) { WRITE("\n\n"); HTML::comment(OUT, I"WORLD WRITE MAP BEGINS"); HTML_OPEN("p"); WRITE("\n"); @; HTML_CLOSE("p"); HTML::comment(OUT, I"WORLD WRITE MAP ENDS"); } } @ We give different colours to the first 20 regions defined, then repeat the cycle for the next 20, and so on. (It's unlikely that there are that many regions, but even if there are, regions 20 apart are unlikely to come into contact, since they would be created in source text a long way distant from each other.) @d NO_REGION_COLOURS 20 @ = wchar_t *some_map_colours[NO_REGION_COLOURS] = { L"Pale Green", L"Light Blue", L"Plum", L"Light Sea Green", L"Light Slate Blue", L"Navajo White", L"Violet Red", L"Light Cyan", L"Light Coral", L"Light Pink", L"Medium Aquamarine", L"Medium Blue", L"Medium Orchid", L"Medium Purple", L"Medium Sea Green", L"Medium Slate Blue", L"Medium Spring Green", L"Medium Turquoise", L"Medium Violet Red", L"Light Golden Rod Yellow" }; faux_instance *RG; int regc = 0; LOOP_OVER_FAUX_REGIONS(faux_set, RG) if (RG->fimd.colour == NULL) { RG->fimd.colour = Str::new(); WRITE_TO(RG->fimd.colour, "%w", HTML::translate_colour_name( some_map_colours[(regc++) % NO_REGION_COLOURS])); } @ = text_stream *default_room_col = Str::new(); WRITE_TO(default_room_col, "%w", HTML::translate_colour_name(L"Light Grey")); faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) if (R->fimd.colour == NULL) { faux_instance *reg = FauxInstances::region_of(R); if (reg) R->fimd.colour = Str::duplicate(reg->fimd.colour); else R->fimd.colour = Str::duplicate(default_room_col); } @ = PL::HTMLMap::begin_variable_width_table(OUT); int z; for (z=Universe.corner1.z; z>=Universe.corner0.z; z--) { @; @; } @; PL::HTMLMap::end_map_table(OUT); @; @ = char *level_rubric = "Map"; int par = 0; PL::HTMLMap::devise_level_rubric(z, &level_rubric, &par); HTML_OPEN("tr"); HTML_OPEN("td"); int rounding = 0; if (z == Universe.corner1.z) rounding = ROUND_BOX_TOP; HTML::open_coloured_box(OUT, "e0e0e0", rounding); WRITE(""); WRITE(level_rubric, par); WRITE(""); HTML::close_coloured_box(OUT, "e0e0e0", rounding); HTML_CLOSE("td"); HTML_CLOSE("tr"); @ = int y_max = -1000000000, y_min = 1000000000; /* assuming there are fewer than 1 billion rooms */ faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) if (Room_position(R).z == z) { if (Room_position(R).y < y_min) y_min = Room_position(R).y; if (Room_position(R).y > y_max) y_max = Room_position(R).y; } if (y_max < y_min) continue; LOGIF(SPATIAL_MAP, "Level %d has rooms with %d <= y <= %d\n", z, y_min, y_max); HTML_OPEN("tr"); HTML_OPEN("td"); PL::HTMLMap::plot_map_level(OUT, Universe.corner0.x, Universe.corner1.x, y_min, y_max, z, 1); HTML_CLOSE("td"); HTML_CLOSE("tr"); WRITE("\n"); @ = HTML_OPEN("tr"); HTML_OPEN("td"); HTML::open_coloured_box(OUT, "e0e0e0", ROUND_BOX_BOTTOM); HTML::close_coloured_box(OUT, "e0e0e0", ROUND_BOX_BOTTOM); HTML_CLOSE("td"); HTML_CLOSE("tr"); @ = faux_instance *D; int k = 0; LOOP_OVER_FAUX_DIRECTIONS(faux_set, D) { faux_instance *A = PL::SpatialMap::mapped_as_if(D); if (A) { k++; if (k == 1) { HTML_OPEN("p"); WRITE("Mapping "); } else WRITE("; "); WRITE("%S as %S", FauxInstances::get_name(D), FauxInstances::get_name(A)); } } if (k > 0) { WRITE(""); HTML_CLOSE("p"); } @h Level rubrics. = void PL::HTMLMap::devise_level_rubric(int z, char **level_rubric, int *par) { *level_rubric = "Map"; *par = 0; switch(Universe.corner1.z - Universe.corner0.z) { case 0: break; case 1: if (z == Universe.corner0.z) *level_rubric = "Lower"; if (z == Universe.corner1.z) *level_rubric = "Upper"; break; default: { int z_offset = z - PL::SpatialMap::benchmark_level(); switch(z_offset) { case 0: *level_rubric = "Starting level"; break; case 1: *level_rubric = "First level up"; break; case -1: *level_rubric = "First level down"; break; case 2: *level_rubric = "Second level up"; break; case -2: *level_rubric = "Second level down"; break; case 3: *level_rubric = "Third level up"; break; case -3: *level_rubric = "Third level down"; break; default: if (z_offset > 0) { *par = z_offset; *level_rubric = "Level %d up"; } if (z_offset < 0) { *par = -z_offset; *level_rubric = "Level %d down"; } break; } break; } } } @h Single-room submaps. The following provides the "details" portion of the World index: there are two columns, the first containing a $1\times 1$ submap of just the room in question, the second containing its indexing details. This will only work if the main routine above has already been called, so that the grids are calculated, the region colours decided, and so on. = void PL::HTMLMap::render_single_room_as_HTML(OUTPUT_STREAM, faux_instance *R) { WRITE("\n\n"); HTML_OPEN("p"); IndexUtilities::anchor(OUT, R->anchor_text); HTML_TAG_WITH("a", "name=wo_%d", R->allocation_id); HTML::begin_plain_html_table(OUT); HTML::first_html_column(OUT, 0); vector P = Room_position(R); PL::HTMLMap::plot_map_level(OUT, P.x, P.x, P.y, P.y, P.z, 2); HTML::next_html_column(OUT, 0); WRITE(" "); HTML::next_html_column(OUT, 0); MapElement::index(OUT, R, 1, FALSE); HTML::end_html_row(OUT); HTML::end_html_table(OUT); HTML_CLOSE("p"); } @h Plotting a rectangle of the map. Either way, then, we end up calling the following routine, which plots a map of a rectangular X-Y area at a given fixed Z coordinate. The pass is 1 for the main mapping, 2 for single-room-only mapping lower down on the index page. = void PL::HTMLMap::plot_map_level(OUTPUT_STREAM, int x0, int x1, int y0, int y1, int z, int pass) { if (pass == 1) LOGIF(SPATIAL_MAP, "Plot: [%d, %d] x [%d, %d] x {%d}\n", x0, x1, y0, y1, z); int with_numbering = FALSE; if ((pass == 1) && (Universe.corner1.z != Universe.corner0.z)) with_numbering = TRUE; WRITE("\n\n"); PL::HTMLMap::begin_variable_width_table_with_background(OUT, "grid.png"); int y, just_dislocated = FALSE; for (y=y1; y>=y0; y--) { int x, c = 0; for (x=x0; x<=x1; x++) if (room_grid[ROOM_GRID_POS(Geometry::vec(x, y, z))]) c++; if (c == 0) { if (just_dislocated == FALSE) { just_dislocated = TRUE; @; } continue; } just_dislocated = FALSE; @; } PL::HTMLMap::end_map_table(OUT); } @ Cells in the map as drawn are divided into three stripes. The top stripe contains the icons for the NW, N, NE exits, the middle stripe the icon for W, then the central square, then the icon for E, and the bottom stripe the three icons for SW, S, SE exits. We can therefore divide the pixel width of a cell as $x_o + x_i + x_o$, where $x_i$ is the width of the central square. It follows that any icon to be plotted in the four corner positions must be square and have pixel dimensions $x_o\times x_o$; icons for the E and W exit positions are $x_o\times x_i$; icons for the N and S positions are $x_i\times x_o$; and the central square is, of course, $x_i\times x_i$, though in fact we don't plot an image there. The grid background must have pixel dimensions $(2x_o+x_i)\times (2x_o+x_i)$. @d MAP_CELL_OUTER_SIZE 13 /* i.e., $x_o$ */ @d MAP_CELL_INNER_SIZE 27 /* i.e., $x_i$ */ @d MAP_CELL_SIZE (MAP_CELL_OUTER_SIZE + MAP_CELL_INNER_SIZE + MAP_CELL_OUTER_SIZE) @ This is going to be a height-19 blank row of a table with a different background image to the regular grid background -- it's an icon of the grid with breaks in it. So we need to end the existing table, start a new one, end it again, and start another table like the original. The cells in a dislocation row have the usual width, but a foreshortened height, and they're drawn with a single stripe. @d MAP_DISLOCATION_HEIGHT 19 /* the reduced height */ @ = PL::HTMLMap::end_map_table(OUT); PL::HTMLMap::begin_variable_width_table_with_background(OUT, "dislocation.png"); HTML_OPEN("tr"); int i, cells = x1-x0+1; if (with_numbering) cells += 2; for (i=0; i = @; @; @; @ The top stripe has height $x_o$. @ = HTML_OPEN("tr"); if (with_numbering) @; for (x=x0; x<=x1; x++) @; if (with_numbering) @ HTML_CLOSE("tr"); @ The middle stripe has height $x_i$. @ = HTML_OPEN("tr"); if (with_numbering) @; for (x=x0; x<=x1; x++) @; if (with_numbering) @; HTML_CLOSE("tr"); @ The bottom stripe has height $x_o$. @ = HTML_OPEN("tr"); if (with_numbering) @ for (x=x0; x<=x1; x++) @; if (with_numbering) @ HTML_CLOSE("tr"); @h Substantive cells. @ = vector P = Geometry::vec(x, y, z); HTML_OPEN("td"); PL::HTMLMap::begin_map_table(OUT, MAP_CELL_SIZE, MAP_CELL_OUTER_SIZE); HTML_OPEN("tr"); HTML_OPEN("td"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 0, 0, 2); if (icon_grid[ICON_GRID_POS(P, 0, 0)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"s_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ns_spacer"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 1, 0, 8); PL::HTMLMap::plot_map_cell(OUT, pass, P, 2, 0, 0); PL::HTMLMap::plot_map_cell(OUT, pass, P, 3, 0, -1); if (icon_grid[ICON_GRID_POS(P, 4, 0)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"s_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ns_spacer"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 4, 0, 1); HTML_CLOSE("td"); HTML_CLOSE("tr"); PL::HTMLMap::end_map_table(OUT); HTML_CLOSE("td"); @ = vector P = Geometry::vec(x, y, z); HTML_OPEN("td"); PL::HTMLMap::begin_map_table(OUT, MAP_CELL_SIZE, MAP_CELL_INNER_SIZE); HTML_OPEN("tr"); HTML_OPEN("td"); PL::HTMLMap::begin_variable_width_table(OUT); HTML_OPEN("tr"); HTML_OPEN("td"); if (icon_grid[ICON_GRID_POS(P, 0, 0)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"e_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ew_spacer"); HTML_TAG("br"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 0, 1, 11); HTML_TAG("br"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 0, 2, 7); HTML_TAG("br"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 0, 3, -1); HTML_TAG("br"); if (icon_grid[ICON_GRID_POS(P, 0, 4)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"e_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ew_spacer"); HTML_CLOSE("td"); HTML_CLOSE("tr"); PL::HTMLMap::end_map_table(OUT); HTML_CLOSE("td"); @; HTML_OPEN("td"); PL::HTMLMap::begin_map_table(OUT, MAP_CELL_OUTER_SIZE, MAP_CELL_INNER_SIZE); HTML_OPEN("tr"); HTML_OPEN("td"); if (icon_grid[ICON_GRID_POS(P, 4, 0)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"w_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ew_spacer"); HTML_TAG("br"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 4, 1, -1); HTML_TAG("br"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 4, 2, 6); HTML_TAG("br"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 4, 3, 10); HTML_TAG("br"); if (icon_grid[ICON_GRID_POS(P, 4, 4)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"w_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ew_spacer"); HTML_CLOSE("td"); HTML_CLOSE("tr"); PL::HTMLMap::end_map_table(OUT); HTML_CLOSE("td"); HTML_CLOSE("tr"); PL::HTMLMap::end_map_table(OUT); HTML_CLOSE("td"); @ The centre of a cell might be a room, or it might be an icon showing the continuation of one or more long connections running through this cell. There are 15 possibilities, and their icons are named as the following shows: @ = HTML_OPEN("td"); int bits = (icon_grid[ICON_GRID_POS(P, 2, 2)]) & LONGS_BITMAP; if (bits == 0) PL::HTMLMap::index_room_square(OUT, room_grid[ROOM_GRID_POS(P)], pass); else { TEMPORARY_TEXT(icon_name) WRITE_TO(icon_name, "long"); if (bits & LONGEW_MAPBIT) WRITE_TO(icon_name, "_ew"); if (bits & LONGNS_MAPBIT) WRITE_TO(icon_name, "_ns"); if (bits & LONGSWNE_MAPBIT) WRITE_TO(icon_name, "_swne"); if (bits & LONGNWSE_MAPBIT) WRITE_TO(icon_name, "_nwse"); PL::HTMLMap::plot_map_icon(OUT, icon_name); DISCARD_TEXT(icon_name) } HTML_CLOSE("td"); @ = vector P = Geometry::vec(x, y, z); HTML_OPEN("td"); PL::HTMLMap::begin_map_table(OUT, MAP_CELL_SIZE, MAP_CELL_OUTER_SIZE); HTML_OPEN("tr"); HTML_OPEN("td"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 0, 4, 5); if (icon_grid[ICON_GRID_POS(P, 0, 4)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"n_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ns_spacer"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 1, 4, -1); PL::HTMLMap::plot_map_cell(OUT, pass, P, 2, 4, 3); PL::HTMLMap::plot_map_cell(OUT, pass, P, 3, 4, 9); if (icon_grid[ICON_GRID_POS(P, 4, 4)] & CONNECTIVE_BITMAP) PL::HTMLMap::plot_map_icon(OUT, I"n_dot"); else PL::HTMLMap::plot_map_icon(OUT, I"ns_spacer"); PL::HTMLMap::plot_map_cell(OUT, pass, P, 4, 4, 4); HTML_CLOSE("td"); HTML_CLOSE("tr"); PL::HTMLMap::end_map_table(OUT); HTML_CLOSE("td"); @h Numbering cells. If we're displaying a numbering in the map, that means there are two columns -- the first and last -- which don't contain rooms or exits, but are simply blank except for an italic row number. @ = HTML_OPEN("td"); PL::HTMLMap::begin_map_table(OUT, MAP_CELL_SIZE, MAP_CELL_OUTER_SIZE); HTML_OPEN("tr"); HTML_OPEN("td"); HTML_CLOSE("td"); HTML_CLOSE("tr"); PL::HTMLMap::end_map_table(OUT); HTML_CLOSE("td"); @ Note that the row number is with respect to the entire Universe, not to the current rectangle being rendered. The two aren't the same, because the rectangle may be for a level in which we've omitted blank rows at the north and south ends. @ = HTML_OPEN("td"); PL::HTMLMap::begin_map_table(OUT, MAP_CELL_SIZE, MAP_CELL_INNER_SIZE); HTML_OPEN("tr"); HTML_OPEN("td"); HTML::begin_colour(OUT, I"c0c0c0"); #ifdef HTML_MAP_FONT_SIZE HTML_OPEN_WITH("span", "style=\"font-size:%dpx;\"", HTML_MAP_FONT_SIZE); #endif HTML_OPEN("center"); HTML_OPEN("i"); WRITE("%d", y-Universe.corner0.y+1); HTML_CLOSE("i"); HTML_CLOSE("center"); #ifdef HTML_MAP_FONT_SIZE HTML_CLOSE("span"); #endif HTML::end_colour(OUT); HTML_CLOSE("td"); HTML_CLOSE("tr"); PL::HTMLMap::end_map_table(OUT); HTML_CLOSE("td"); @h Plotting the eight exterior icons. That leaves just the low-level routines to handle the nine individual pieces of the cell. First, the eight cells around the outside: = void PL::HTMLMap::plot_map_cell(OUTPUT_STREAM, int pass, vector P, int i1, int i2, int faux_exit) { faux_instance_set *faux_set = InterpretIndex::get_faux_instances(); int bitmap = icon_grid[ICON_GRID_POS(P, i1, i2)]; if (pass == 2) bitmap &= CONNECTIVE_BITMAP; if (bitmap == 0) @ else @; } @ = if ((i1 == 1) || (i1 == 3)) PL::HTMLMap::plot_map_icon(OUT, I"blank_ns"); else { if ((i2 == 1) || (i2 == 3)) PL::HTMLMap::plot_map_icon(OUT, I"blank_ew"); else PL::HTMLMap::plot_map_icon(OUT, I"blank_square"); } @ = int exit = exit_grid[ICON_GRID_POS(P, i1, i2)]; TEMPORARY_TEXT(icon_name) TEMPORARY_TEXT(tool_tip) @; @; if (Str::len(tool_tip) > 0) PL::HTMLMap::plot_map_icon_with_tip(OUT, icon_name, tool_tip); else PL::HTMLMap::plot_map_icon(OUT, icon_name); DISCARD_TEXT(icon_name) DISCARD_TEXT(tool_tip) @ = char *clue = PL::SpatialMap::find_icon_label(exit); if (clue == NULL) clue = PL::SpatialMap::find_icon_label(faux_exit); if (clue == NULL) clue = ""; /* should never happen */ char *addendum = ""; if (bitmap & DOOR2_MAPBIT) { addendum = "_door"; if (bitmap & MEET_MAPBIT) addendum = "_door_meet"; } if (bitmap & DOOR1_MAPBIT) addendum = "_door_blocked"; if (bitmap & CROSSDOOR_MAPBIT) addendum = "_corner_door"; if (bitmap & CROSSDOT_MAPBIT) addendum = "_dot"; if ((addendum[0] == 0) && (bitmap & FADING_MAPBIT)) addendum = "_fading"; WRITE_TO(icon_name, "%s_arrow%s", clue, addendum); @ = faux_instance *D = NULL; faux_instance *I3 = PL::SpatialMap::room_exit(room_grid[ROOM_GRID_POS(P)], exit, &D); if ((I3) || (D)) { WRITE_TO(tool_tip, "title=\""); faux_instance *I; LOOP_OVER_FAUX_INSTANCES(faux_set, I) if (I->direction_index == exit) { FauxInstances::write_name(tool_tip, I); break; } if (D) { if (I3 == NULL) WRITE_TO(tool_tip, " exit blocked by "); else WRITE_TO(tool_tip, " through "); FauxInstances::write_name(tool_tip, D); } if (I3) { WRITE_TO(tool_tip, " to "); FauxInstances::write_name(tool_tip, I3); } WRITE_TO(tool_tip, "\""); } @h Plotting the single central square. The following routine renders the square icons for the rooms themselves, which are bordered and coloured single-cell tables. @d ROOM_BORDER_SIZE 1 @d B_ROOM_BORDER_SIZE 2 @d ROOM_BORDER_COLOUR "000000" @d ROOM_TEXT_COLOUR "000000" = void PL::HTMLMap::index_room_square(OUTPUT_STREAM, faux_instance *I, int pass) { if (I) { int b = ROOM_BORDER_SIZE; if ((I == FauxInstances::benchmark()) && (pass == 1)) b = B_ROOM_BORDER_SIZE; HTML_OPEN_WITH("table", "border=\"%d\" cellpadding=\"0\" cellspacing=\"0\" " "bordercolor=\"#%s\" width=\"%d\" height=\"%d\" " "title=\"%S\"", b, ROOM_BORDER_COLOUR, MAP_CELL_INNER_SIZE, MAP_CELL_INNER_SIZE, FauxInstances::get_name(I)); HTML_OPEN("tr"); HTML_OPEN_WITH("td", "valign=\"middle\" align=\"center\" bgcolor=\"#%S\"", I->fimd.colour); TEMPORARY_TEXT(col) if (I->fimd.text_colour) WRITE_TO(col, "%S", I->fimd.text_colour); else WRITE_TO(col, "%s", ROOM_TEXT_COLOUR); HTML::begin_colour(OUT, col); @; HTML::end_colour(OUT); HTML_CLOSE("td"); HTML_CLOSE("tr"); HTML_CLOSE("table"); WRITE("\n"); DISCARD_TEXT(col) } } @ = if (pass == 1) { HTML_OPEN_WITH("a", "href=#wo_%d style=\"text-decoration: none\"", I->allocation_id); HTML::begin_colour(OUT, col); } if ((pass == 1) && (I == FauxInstances::benchmark())) HTML_OPEN("b"); TEMPORARY_TEXT(abbrev) WRITE_TO(abbrev, "%S", I->abbrev); #ifdef HTML_MAP_FONT_SIZE HTML_OPEN_WITH("span", "style=\"font-size:%dpx;\"", HTML_MAP_FONT_SIZE); #endif LOOP_THROUGH_TEXT(pos, abbrev) HTML::put(OUT, Str::get(pos)); #ifdef HTML_MAP_FONT_SIZE HTML_CLOSE("span"); #endif if ((pass == 1) && (I == FauxInstances::benchmark())) HTML_CLOSE("b"); if (pass == 1) { HTML::end_colour(OUT); HTML_CLOSE("a"); } DISCARD_TEXT(abbrev) @h The colour chip. The first of two extras, which aren't strictly speaking part of the HTML map. This is the chip shown on the "details" box for a room in the World Index. = void PL::HTMLMap::colour_chip(OUTPUT_STREAM, faux_instance *I, faux_instance *Reg, int at) { HTML_OPEN_WITH("table", "border=\"%d\" cellpadding=\"0\" cellspacing=\"0\" " "bordercolor=\"#%s\" height=\"%d\"", ROOM_BORDER_SIZE, ROOM_BORDER_COLOUR, MAP_CELL_INNER_SIZE); HTML_OPEN("tr"); HTML_OPEN_WITH("td", "valign=\"middle\" align=\"center\" bgcolor=\"#%S\"", Reg->fimd.colour); WRITE(" "); FauxInstances::write_name(OUT, Reg); WRITE(" region"); if (at > 0) IndexUtilities::link(OUT, at); WRITE(" "); HTML_CLOSE("td"); HTML_CLOSE("tr"); HTML_CLOSE("table"); WRITE("\n"); } @h The regions key. The part of the World Index showing which rooms belong to which regions. Note that nothing is shown if all of the rooms are outside of regions. = void PL::HTMLMap::add_region_key(OUTPUT_STREAM) { faux_instance_set *faux_set = InterpretIndex::get_faux_instances(); faux_instance *reg; int count = 0; LOOP_OVER_FAUX_REGIONS(faux_set, reg) count += PL::HTMLMap::add_key_for(OUT, reg); if (count > 0) count += PL::HTMLMap::add_key_for(OUT, NULL); if (count > 0) HTML_TAG("hr"); } int PL::HTMLMap::add_key_for(OUTPUT_STREAM, faux_instance *reg) { faux_instance_set *faux_set = InterpretIndex::get_faux_instances(); int count = 0; faux_instance *R; LOOP_OVER_FAUX_ROOMS(faux_set, R) { if (FauxInstances::region_of(R) == reg) { if (count++ == 0) { @; } else { WRITE(", "); } WRITE("%S", FauxInstances::get_name(R)); } } if (count > 0) @; return count; } @ = HTML_OPEN("p"); HTML::begin_plain_html_table(OUT); HTML_OPEN("tr"); WRITE("\n"); HTML_OPEN_WITH("td", "width=\"40\" valign=\"middle\" align=\"left\""); PL::HTMLMap::index_room_square(OUT, R, 1); HTML_CLOSE("td"); WRITE("\n"); HTML_OPEN_WITH("td", "valign=\"middle\" align=\"left\""); WRITE(""); if (reg) WRITE("%S", FauxInstances::get_name(reg)); else WRITE("Not in any region"); WRITE(": "); @ = HTML::end_html_row(OUT); HTML::end_html_table(OUT); HTML_CLOSE("p");