diff --git a/src/citymania/highlight.cpp b/src/citymania/highlight.cpp index 45eccd56ec..147a3b4a7d 100644 --- a/src/citymania/highlight.cpp +++ b/src/citymania/highlight.cpp @@ -23,7 +23,7 @@ extern void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *t extern const Station *_viewport_highlight_station; extern TileHighlightData _thd; extern bool IsInsideSelectedRectangle(int x, int y); - +extern void MarkCatchmentTilesDirty(); namespace citymania { struct TileZoning { @@ -169,7 +169,8 @@ static void SetStationSelectionHighlight(const TileInfo *ti, TileHighlight &th) }; auto b = CalcTileBorders(ti->tile, coverage_getter); if (b.second) { - const SpriteID pal[] = {PAL_NONE, SPR_PALETTE_ZONING_WHITE, SPR_PALETTE_ZONING_LIGHT_BLUE}; + // const SpriteID pal[] = {PAL_NONE, SPR_PALETTE_ZONING_WHITE, SPR_PALETTE_ZONING_LIGHT_BLUE}; + const SpriteID pal[] = {PAL_NONE, SPR_PALETTE_ZONING_WHITE, PAL_NONE}; th.add_border(b.first, pal[b.second]); const SpriteID pal2[] = {PAL_NONE, PALETTE_TINT_WHITE, PALETTE_TINT_BLUE}; th.ground_pal = th.structure_pal = pal2[b.second]; @@ -177,7 +178,6 @@ static void SetStationSelectionHighlight(const TileInfo *ti, TileHighlight &th) if (_station_to_join) { auto b = CalcTileBorders(ti->tile, [](TileIndex t) { - return _station_to_join_area.Contains(t) ? 1 : 0; }); th.add_border(b.first, SPR_PALETTE_ZONING_LIGHT_BLUE); @@ -428,7 +428,7 @@ void SetIndustryForbiddenTilesHighlight(IndustryType type) { // _station_select.catchment = catchment; // } -void SetStationBiildingStatus(SetStationBiildingStatus status) { +void SetStationBiildingStatus(StationBuildingStatus status) { _station_building_status = status; }; @@ -456,15 +456,17 @@ void MarkTileAreaDirty(const TileArea &ta) { } static void UpdateStationToJoinArea() { - auto &r = station->rect; + auto &r = _station_to_join->rect; auto d = (int)_settings_game.station.station_spread - 1; - ta = TileArea( + TileArea ta( TileXY(max(r.right - d, 0), max(r.bottom - d, 0)), TileXY(min(r.left + d, MapSizeX() - 1), min(r.top + d, MapSizeY() - 1)) ); - if (_station_to_join_area == ta) return; + if (_station_to_join_area.tile == ta.tile && + _station_to_join_area.w == ta.w && + _station_to_join_area.h == ta.h) return; _station_to_join_area = ta; MarkTileAreaDirty(_station_to_join_area); } diff --git a/src/citymania/highlight.hpp b/src/citymania/highlight.hpp index 6b49d3221b..69c12ca5b4 100644 --- a/src/citymania/highlight.hpp +++ b/src/citymania/highlight.hpp @@ -45,7 +45,7 @@ public: uint border_count = 0; void add_border(ZoningBorder border, SpriteID color) { - if (border == ZoningBorder::NONE) return; + if (border == ZoningBorder::NONE || !color) return; this->border[this->border_count] = border; this->border_color[this->border_count] = color; this->border_count++; diff --git a/src/rev.cpp b/src/rev.cpp index 2a7ce919f6..fce3f0b397 100644 --- a/src/rev.cpp +++ b/src/rev.cpp @@ -83,4 +83,4 @@ const byte _openttd_revision_tagged = 1; const uint32 _openttd_newgrf_version = 1 << 28 | 10 << 24 | 0 << 20 | 0 << 19 | 28004; -const char _citymania_version[] = "20200207-master-m20f19618cf 07.02.20"; +const char _citymania_version[] = "20200215-master-m5217e7c98b 15.02.20"; diff --git a/src/station_gui.cpp b/src/station_gui.cpp index fcf2f16361..3280673877 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -121,7 +121,7 @@ static void FindStationsAroundSelection() TileArea ta(TileXY(max(0, x - max_c), max(0, y - max_c)), TileXY(min(MapMaxX(), x + location.w + max_c), min(MapMaxY(), y + location.h + max_c))); Station *adjacent = nullptr; - auto cmbp = citymania::StationBulidingStatus::NEW; + auto cmbp = citymania::StationBuildingStatus::NEW; /* Direct loop instead of FindStationsAroundTiles as we are not interested in catchment area */ TILE_AREA_LOOP(tile, ta) { @@ -131,14 +131,14 @@ static void FindStationsAroundSelection() if (adjacent != nullptr && st != adjacent) { /* Multiple nearby, distant join is required. */ adjacent = nullptr; - cmbp =(_ctrl_pressed ? citymania::BuildingPossibility::QUERY : citymania::BuildingPossibility::IMPOSSIBLE); + cmbp =(_ctrl_pressed ? citymania::StationBuildingStatus::QUERY : citymania::StationBuildingStatus::IMPOSSIBLE); break; } adjacent = st; } } SetViewportCatchmentStation(adjacent, true); - citymania::SetStationBiildingStatus(citymania::StationBulidingStatus::JOIN); + citymania::SetStationBiildingStatus(citymania::StationBuildingStatus::JOIN); } /** @@ -2455,7 +2455,7 @@ struct SelectStationWindow : WindowPopup { { if (widget != WID_JS_PANEL || T::EXPECTED_FACIL == FACIL_WAYPOINT) { SetViewportCatchmentStation(nullptr, true); - citymania::SetStationBiildingStatus(citymania::StationBulidingStatus::QUERY); + citymania::SetStationBiildingStatus(citymania::StationBuildingStatus::QUERY); return; } @@ -2463,11 +2463,11 @@ struct SelectStationWindow : WindowPopup { uint st_index = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_JS_PANEL, WD_FRAMERECT_TOP); if (st_index == 0 || st_index > _stations_nearby_list.size()) { SetViewportCatchmentStation(nullptr, true); - citymania::SetStationBiildingStatus(citymania::StationBulidingStatus::NEW); + citymania::SetStationBiildingStatus(citymania::StationBuildingStatus::NEW); } else { st_index--; SetViewportCatchmentStation(Station::Get(_stations_nearby_list[st_index]), true); - citymania::SetStationBiildingStatus(citymania::StationBulidingStatus::JOIN); + citymania::SetStationBiildingStatus(citymania::StationBuildingStatus::JOIN); } } }; diff --git a/src/viewport.cpp b/src/viewport.cpp index f93b504625..01cce20cdf 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -92,7 +92,9 @@ #include "network/network_func.h" #include "framerate_type.h" +#include #include +#include #include "table/strings.h" #include "table/string_colours.h" @@ -769,7 +771,6 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, ps.zmin = z + bb_offset_z; ps.zmax = z + max(bb_offset_z, dz) - 1; - ps.comparison_done = false; ps.first_child = -1; _vd.last_child = &ps.first_child; @@ -1548,64 +1549,127 @@ static bool ViewportSortParentSpritesChecker() return true; } -/** Sort parent sprites pointer array */ +/** Sort parent sprites pointer array replicating the way original sorter did it. */ static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv) { - auto psdvend = psdv->end(); - auto psd = psdv->begin(); - while (psd != psdvend) { - ParentSpriteToDraw *ps = *psd; + if (psdv->size() < 2) return; - if (ps->comparison_done) { - psd++; + /* We rely on sprites being, for the most part, already ordered. + * So we don't need to move many of them and can keep track of their + * order effecienty by using stack. We always move sprites to the front + * of the current position, i.e. to the top of the stack. + * Also use special constants to indicate sorting state without + * adding extra fields to ParentSpriteToDraw structure. + */ + const uint32 ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it + const uint32 ORDER_RETURNED = UINT32_MAX - 1; // Makr sorted sprite in case there are other occurences of it in the stack + std::stack sprite_order; + uint32 next_order = 0; + + std::forward_list> sprite_list; // We store sprites in a list sorted by xmin+ymin + + /* Initialize sprite list and order. */ + for (auto p = psdv->rbegin(); p != psdv->rend(); p++) { + sprite_list.push_front(std::make_pair((*p)->xmin + (*p)->ymin, *p)); + sprite_order.push(*p); + (*p)->order = next_order++; + } + + sprite_list.sort(); + + std::vector preceding; // Temporarily stores sprites that precede current and their position in the list + auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preciding sprite + auto out = psdv->begin(); // Iterator to output sorted sprites + + while (!sprite_order.empty()) { + + auto s = sprite_order.top(); + sprite_order.pop(); + + /* Sprite is already sorted, ignore it. */ + if (s->order == ORDER_RETURNED) continue; + + /* Sprite was already compared, just need to output it. */ + if (s->order == ORDER_COMPARED) { + *(out++) = s; + s->order = ORDER_RETURNED; continue; } - ps->comparison_done = true; + preceding.clear(); - for (auto psd2 = psd + 1; psd2 != psdvend; psd2++) { - ParentSpriteToDraw *ps2 = *psd2; + /* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax + * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax + * we get all we need and some more that we filter out later. + * We don't include zmin into the sum as there are usually more neighbors on x and y than z + * so including it will actually increase the amount of false posistives. + * Also min coordinates can be > xmax so use max(xmin, xmax) + max(ymin, ymax) + * to ensure we terate the current sprite as we need to remove it from the list. + */ + auto ssum = max(s->xmax, s->xmin) + max(s->ymax, s->ymin); + auto prev = sprite_list.before_begin(); + auto x = sprite_list.begin(); + while(x != sprite_list.end() && ((*x).first <= ssum)) { + auto p = (*x).second; + if (p == s) { + /* We found the current sprite, remove it and move on. */ + x = sprite_list.erase_after(prev); + continue; + } - if (ps2->comparison_done) continue; + auto p_prev = prev; + prev = x++; - /* Decide which comparator to use, based on whether the bounding - * boxes overlap - */ - if (ps->xmax >= ps2->xmin && ps->xmin <= ps2->xmax && // overlap in X? - ps->ymax >= ps2->ymin && ps->ymin <= ps2->ymax && // overlap in Y? - ps->zmax >= ps2->zmin && ps->zmin <= ps2->zmax) { // overlap in Z? - /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of - * the screen and with higher Z elevation, are drawn in front. - * Here X,Y,Z are the coordinates of the "center of mass" of the sprite, - * i.e. X=(left+right)/2, etc. - * However, since we only care about order, don't actually divide / 2 - */ - if (ps->xmin + ps->xmax + ps->ymin + ps->ymax + ps->zmin + ps->zmax <= - ps2->xmin + ps2->xmax + ps2->ymin + ps2->ymax + ps2->zmin + ps2->zmax) { - continue; - } - } else { - /* We only change the order, if it is definite. - * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap. - * That is: If one partial order says ps behind ps2, do not change the order. - */ - if (ps->xmax < ps2->xmin || - ps->ymax < ps2->ymin || - ps->zmax < ps2->zmin) { + if (s->xmax < p->xmin || s->ymax < p->ymin || s->zmax < p->zmin) continue; + if (s->xmin <= p->xmax && // overlap in X? + s->ymin <= p->ymax && // overlap in Y? + s->zmin <= p->zmax) { // overlap in Z? + if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <= + p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) { continue; } } + preceding.push_back(p); + preceding_prev = p_prev; + } - /* Move ps2 in front of ps */ - ParentSpriteToDraw *temp = ps2; - for (auto psd3 = psd2; psd3 > psd; psd3--) { - *psd3 = *(psd3 - 1); + if (preceding.empty()) { + /* No preceding sprites, add current one to the output */ + *(out++) = s; + s->order = ORDER_RETURNED; + continue; + } + + /* Optimization for the case when we only have 1 sprite to move. */ + if (preceding.size() == 1) { + auto p = preceding[0]; + /* We can only output the preceding sprite if there can't be any other sprites preceding it. */ + if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) { + p->order = ORDER_RETURNED; + s->order = ORDER_RETURNED; + sprite_list.erase_after(preceding_prev); + *(out++) = p; + *(out++) = s; + continue; } - *psd = temp; + } + + /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */ + std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) { + return a->order > b->order; + }); + + s->order = ORDER_COMPARED; + sprite_order.push(s); // Still need to output so push it back for now + + for (auto p: preceding) { + p->order = next_order++; + sprite_order.push(p); } } } + static void ViewportDrawParentSprites(const ParentSpriteToSortVector *psd, const ChildScreenSpriteToDrawVector *csstdv) { for (const ParentSpriteToDraw *ps : *psd) { @@ -3834,7 +3898,7 @@ CommandCost CmdScrollViewport(TileIndex tile, DoCommandFlag flags, uint32 p1, ui return CommandCost(); } -static void MarkCatchmentTilesDirty() +void MarkCatchmentTilesDirty() { if (_viewport_highlight_town != nullptr) { MarkWholeScreenDirty();