summaryrefslogtreecommitdiff
path: root/src/viewport.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/viewport.cpp')
-rw-r--r--src/viewport.cpp429
1 files changed, 362 insertions, 67 deletions
diff --git a/src/viewport.cpp b/src/viewport.cpp
index bfcc8d9d1..10cc8f360 100644
--- a/src/viewport.cpp
+++ b/src/viewport.cpp
@@ -28,6 +28,8 @@
#include "stdafx.h"
#include "landscape.h"
+#include "layer_gui.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "station_base.h"
#include "waypoint_base.h"
@@ -51,6 +53,13 @@
Point _tile_fract_coords;
+struct RailTrackEndpoint {
+ TileIndex tile;
+ TrackdirBits dirs;
+};
+
+RailTrackEndpoint _rail_track_endpoints[4];
+
struct StringSpriteToDraw {
StringID string;
Colours colour;
@@ -1080,6 +1089,8 @@ static void ViewportAddLandscape()
y_cur < MapMaxY() * TILE_SIZE) {
TileIndex tile = TileVirtXY(x_cur, y_cur);
+ /* Валидны только клетки текущего слоя */
+ if (LayerIndex(tile) == _vd.dpi.layer)
if (!_settings_game.construction.freeform_edges || (TileX(tile) != 0 && TileY(tile) != 0)) {
if (x_cur == ((int)MapMaxX() - 1) * TILE_SIZE || y_cur == ((int)MapMaxY() - 1) * TILE_SIZE) {
uint maxh = max<uint>(TileHeight(tile), 1);
@@ -1475,6 +1486,9 @@ void ViewportDoDraw(const ViewPort *vp, int left, int top, int right, int bottom
_vd.dpi.dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top);
+ /* Определение слоя (который будем рисовать) */
+ _vd.dpi.layer = calculateLayer(vp);
+
ViewportAddLandscape();
ViewportAddVehicles(&_vd.dpi);
@@ -1953,7 +1967,7 @@ static void PlaceObject()
}
-bool HandleViewportClicked(const ViewPort *vp, int x, int y)
+bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click)
{
const Vehicle *v = CheckClickOnVehicle(vp, x, y);
@@ -1961,6 +1975,13 @@ bool HandleViewportClicked(const ViewPort *vp, int x, int y)
if (v != NULL && VehicleClicked(v)) return true;
}
+ /* Double-clicking finishes current polyline and starts new one. */
+ if (double_click && (_thd.place_mode & HT_POLY)) {
+ ClearRailPlacementEndpoints();
+ SetTileSelectSize(1, 1);
+ return true;
+ }
+
/* Vehicle placement mode already handled above. */
if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
PlaceObject();
@@ -2113,7 +2134,7 @@ Window *TileHighlightData::GetCallbackWnd()
return FindWindowById(this->window_class, this->window_number);
}
-
+static HighLightStyle CalcPolyrailDrawstyle(Point pt);
/**
* Updates tile highlighting for all cases.
@@ -2170,28 +2191,42 @@ void UpdateTileSelection()
y1 += TILE_SIZE / 2;
break;
case HT_RAIL:
- /* Draw one highlighted tile in any direction */
- new_drawstyle = GetAutorailHT(pt.x, pt.y);
- break;
case HT_LINE:
- switch (_thd.place_mode & HT_DIR_MASK) {
- case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
- case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
-
- case HT_DIR_HU:
- case HT_DIR_HL:
- new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
- break;
-
- case HT_DIR_VL:
- case HT_DIR_VR:
- new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
+ /* Handle polyline highlight */
+ if (_thd.place_mode & HT_POLY) {
+ new_drawstyle = CalcPolyrailDrawstyle(pt);
+ if (new_drawstyle != HT_NONE) {
+ x1 = min(_thd.selstart.x, _thd.selend.x);
+ y1 = min(_thd.selstart.y, _thd.selend.y);
+ _thd.new_size.x = abs<int>(_thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK)) + TILE_SIZE;
+ _thd.new_size.y = abs<int>(_thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK)) + TILE_SIZE;
break;
-
- default: NOT_REACHED();
+ }
+ }
+ /* Handle regular (non-polyline) highlight */
+ if (_thd.place_mode & HT_RAIL) {
+ /* Draw one highlighted tile in any direction */
+ new_drawstyle = GetAutorailHT(pt.x, pt.y);
+ } else { // HT_LINE
+ switch (_thd.place_mode & HT_DIR_MASK) {
+ case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
+ case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
+
+ case HT_DIR_HU:
+ case HT_DIR_HL:
+ new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
+ break;
+
+ case HT_DIR_VL:
+ case HT_DIR_VR:
+ new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
+ break;
+
+ default: NOT_REACHED();
+ }
+ _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
+ _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
}
- _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
- _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
break;
default:
NOT_REACHED();
@@ -2455,7 +2490,31 @@ static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_t
return (int)(h1 - h0) * TILE_HEIGHT_STEP;
}
-static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF};
+static void ShowLengthMeasurement(HighLightStyle style, TileIndex start_tile, TileIndex end_tile, TooltipCloseCondition close_cond = TCC_LEFT_CLICK, bool show_single_tile_length = false)
+{
+ static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF};
+
+ if (_settings_client.gui.measure_tooltip) {
+ uint distance = DistanceManhattan(start_tile, end_tile) + 1;
+ byte index = 0;
+ uint64 params[2];
+
+ if (show_single_tile_length || distance != 1) {
+ int heightdiff = CalcHeightdiff(style, distance, start_tile, end_tile);
+ /* If we are showing a tooltip for horizontal or vertical drags,
+ * 2 tiles have a length of 1. To bias towards the ceiling we add
+ * one before division. It feels more natural to count 3 lengths as 2 */
+ if ((style & HT_DIR_MASK) != HT_DIR_X && (style & HT_DIR_MASK) != HT_DIR_Y) {
+ distance = CeilDiv(distance, 2);
+ }
+
+ params[index++] = distance;
+ if (heightdiff != 0) params[index++] = heightdiff;
+ }
+
+ ShowMeasurementTooltips(measure_strings_length[index], index, params, close_cond);
+ }
+}
/**
* Check for underflowing the map.
@@ -2486,8 +2545,178 @@ static void CheckOverflow(int &test, int &other, int max, int mult)
test = max;
}
+static const struct {
+ Point start_point_offset;
+ Point direction;
+}
+_auto_line_by_trackdir[] = {
+ { { TILE_SIZE - 2, TILE_SIZE / 2 }, { -1, 0 } }, // TRACKDIR_X_NE
+ { { TILE_SIZE / 2, 0 }, { 0, +1 } }, // TRACKDIR_Y_SE
+ { { TILE_SIZE / 2 - 1, 0 }, { -1, +1 } }, // TRACKDIR_UPPER_E
+ { { TILE_SIZE - 1, TILE_SIZE / 2 }, { -1, +1 } }, // TRACKDIR_LOWER_E
+ { { TILE_SIZE / 2, 0 }, { +1, +1 } }, // TRACKDIR_LEFT_S
+ { { 0, TILE_SIZE / 2 }, { +1, +1 } }, // TRACKDIR_RIGHT_S
+ { { 0, 0 }, { 0, 0 } }, // TRACKDIR_RVREV_NE
+ { { 0, 0 }, { 0, 0 } }, // TRACKDIR_RVREV_SE
+ { { 0, TILE_SIZE / 2 }, { +1, 0 } }, // TRACKDIR_X_SW
+ { { TILE_SIZE / 2, TILE_SIZE - 1 }, { 0, -1 } }, // TRACKDIR_Y_NW
+ { { 0, TILE_SIZE / 2 - 1 }, { +1, -1 } }, // TRACKDIR_UPPER_W
+ { { TILE_SIZE / 2, TILE_SIZE - 1 }, { +1, -1 } }, // TRACKDIR_LOWER_W
+ { { TILE_SIZE - 1, TILE_SIZE / 2 - 1 }, { -1, -1 } }, // TRACKDIR_LEFT_N
+ { { TILE_SIZE / 2 - 1, TILE_SIZE - 1 }, { -1, -1 } } // TRACKDIR_RIGHT_N
+};
+
+/**
+ * Returns the distnce from a given point to a rail line.
+ *
+ * @param pt The point to get the distance from.
+ * @param start_tile Coordinates, in tile "units", of the tile where the line starts.
+ * @param trackdir The first trackdir of the line.
+ * @return X/Y coordinates of the vector that connects the 'pt' point with the line at the best path.
+ */
+static Point GetDistanceToAutoLine(Point pt, Point start_tile, Trackdir trackdir)
+{
+ assert(IsValidTrackdir(trackdir) && !IsReversingRoadTrackdir(trackdir));
+
+ /* calculate distance from the given point to the point where the line starts */
+ Point d = {
+ start_tile.x + _auto_line_by_trackdir[trackdir].start_point_offset.x - pt.x,
+ start_tile.y + _auto_line_by_trackdir[trackdir].start_point_offset.y - pt.y
+ };
+ /* get line direction */
+ Point direction = _auto_line_by_trackdir[trackdir].direction;
+ /* correct the start point for "diagonal" dirs; there are two possible lines, choose the closer one */
+ if (direction.x == 0) { // TRACKDIR_Y_SE and TRACKDIR_Y_NW trackdirs
+ d.x -= (int)(d.x > 0);
+ } else if (direction.y == 0) { // TRACKDIR_X_NE and TRACKDIR_X_SW trackdirs
+ d.y -= (int)(d.y > 0);
+ }
+
+ /* calculate distance to the end of the line */
+ int scale = direction.x * direction.x + direction.y * direction.y;
+ int length = d.x * direction.y - d.y * direction.x;
+ Point ret = { direction.y, -direction.x }; // 'direction' rotated 90 degree right
+ if (length > 0) { // is the 'p' point on the left side of the line ("up" is pointed by 'direction')
+ ret.x -= direction.x;
+ ret.y -= direction.y;
+ } else {
+ ret.x += direction.x;
+ ret.y += direction.y;
+ }
+ ret.x = length * ret.x / scale;
+ ret.y = length * ret.y / scale;
+
+ /* test if the calculated end point is behind the start point;
+ * if not return the distance to the start point */
+ if (((d.x < ret.x) == (direction.x < 0)) && ((d.y < ret.y) == (direction.y < 0))) return d;
+
+ return ret;
+}
+
+static void ClampAutoLineToMapBorders(Point *line_end, Track line_orientation)
+{
+ int padding = _settings_game.construction.freeform_edges ? TILE_SIZE : 0;
+
+ Rect borders = {
+ padding, // left
+ padding, // top
+ MapSizeX() * TILE_SIZE - padding - 1, // right
+ MapSizeY() * TILE_SIZE - padding - 1 // bottom
+ };
+
+ switch (line_orientation) {
+ case TRACK_X:
+ line_end->y = Clamp(line_end->y, borders.top, borders.bottom);
+ break;
+
+ case TRACK_Y:
+ line_end->x = Clamp(line_end->x, borders.left, borders.right);
+ break;
+
+ case TRACK_UPPER:
+ case TRACK_LOWER:
+ if (line_end->x < borders.left) {
+ line_end->y += borders.left - line_end->x;
+ line_end->x += borders.left - line_end->x;
+ } else if (line_end->x > borders.right) {
+ line_end->y += borders.right - line_end->x;
+ line_end->x += borders.right - line_end->x;
+ }
+ if (line_end->y < borders.top) {
+ line_end->x += borders.top - line_end->y;
+ line_end->y += borders.top - line_end->y;
+ } else if (line_end->y > borders.bottom) {
+ line_end->x += borders.bottom - line_end->y;
+ line_end->y += borders.bottom - line_end->y;
+ }
+ break;
+
+ case TRACK_LEFT:
+ case TRACK_RIGHT:
+ if (line_end->x < borders.left) {
+ line_end->y -= borders.left - line_end->x;
+ line_end->x += borders.left - line_end->x;
+ } else if (line_end->x > borders.right) {
+ line_end->y -= borders.right - line_end->x;
+ line_end->x += borders.right - line_end->x;
+ }
+ if (line_end->y < borders.top) {
+ line_end->x -= borders.top - line_end->y;
+ line_end->y += borders.top - line_end->y;
+ } else if (line_end->y > borders.bottom) {
+ line_end->x -= borders.bottom - line_end->y;
+ line_end->y += borders.bottom - line_end->y;
+ }
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+
+ assert(IsInsideMM(line_end->x, borders.left, borders.right + 1) && IsInsideMM(line_end->y, borders.top, borders.bottom + 1));
+}
+
+static const TrackdirBits _autoline_dirs_allowed_by_highlight_dir[] = {
+ TRACKDIR_BIT_X_NE | TRACKDIR_BIT_X_SW, // HT_DIR_X
+ TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_Y_SE, // HT_DIR_Y
+ TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_W, // HT_DIR_HU
+ TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_W, // HT_DIR_HL
+ TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_N | TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_S, // HT_DIR_VL
+ TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_N | TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_S, // HT_DIR_VR
+};
+
+static Trackdir FindBestAutoLine(const Point &pt, RailTrackEndpoint *start_points, uint num_start_points, TrackdirBits allowed_trackdirs, Point *ret_start_tile, Point *ret_end_pos)
+{
+ Trackdir ret = INVALID_TRACKDIR;
+ uint best_distance = UINT_MAX;
+
+ for (; num_start_points-- > 0; start_points++) {
+ /* skip invalid tiles */
+ if (!IsValidTile(start_points->tile)) continue;
+
+ Trackdir trackdir;
+ FOR_EACH_SET_TRACKDIR(trackdir, start_points->dirs & allowed_trackdirs) {
+ Point start_tile = { TileX(start_points->tile) * TILE_SIZE, TileY(start_points->tile) * TILE_SIZE };
+ Point offset = GetDistanceToAutoLine(pt, start_tile, trackdir);
+ uint distance = (uint)(offset.x * offset.x + offset.y * offset.y);
+ if (distance < best_distance) {
+ *ret_start_tile = start_tile;
+ ret_end_pos->x = pt.x + offset.x;
+ ret_end_pos->y = pt.y + offset.y;
+ best_distance = distance;
+ ret = trackdir;
+ }
+ }
+ }
+
+ /* cut the line at map borders */
+ if (ret != INVALID_TRACKDIR) ClampAutoLineToMapBorders(ret_end_pos, TrackdirToTrack(ret));
+
+ return ret;
+}
+
/** while dragging */
-static void CalcRaildirsDrawstyle(int x, int y, int method)
+static void CalcRaildirsDrawstyle(int x, int y, ViewportPlaceMethod method)
{
HighLightStyle b;
@@ -2672,32 +2901,31 @@ static void CalcRaildirsDrawstyle(int x, int y, int method)
}
}
- if (_settings_client.gui.measure_tooltip) {
- TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
- TileIndex t1 = TileVirtXY(x, y);
- uint distance = DistanceManhattan(t0, t1) + 1;
- byte index = 0;
- uint64 params[2];
+ _thd.selend.x = x;
+ _thd.selend.y = y;
+ _thd.next_drawstyle = b;
- if (distance != 1) {
- int heightdiff = CalcHeightdiff(b, distance, t0, t1);
- /* If we are showing a tooltip for horizontal or vertical drags,
- * 2 tiles have a length of 1. To bias towards the ceiling we add
- * one before division. It feels more natural to count 3 lengths as 2 */
- if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
- distance = CeilDiv(distance, 2);
- }
+ ShowLengthMeasurement(b, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
+}
- params[index++] = distance;
- if (heightdiff != 0) params[index++] = heightdiff;
- }
+static HighLightStyle CalcPolyrailDrawstyle(Point pt)
+{
+ /* directions allowed by highlight method */
+ TrackdirBits allowed_trackdirs = (_thd.place_mode & HT_RAIL) ? TRACKDIR_BIT_MASK : _autoline_dirs_allowed_by_highlight_dir[_thd.place_mode & HT_DIR_MASK];
- ShowMeasurementTooltips(measure_strings_length[index], index, params);
- }
+ /* now find the best track */
+ Trackdir best_trackdir = FindBestAutoLine(pt, _rail_track_endpoints, lengthof(_rail_track_endpoints), allowed_trackdirs, &_thd.selstart, &_thd.selend);
+ if (best_trackdir == INVALID_TRACKDIR) return HT_NONE; // no match
- _thd.selend.x = x;
- _thd.selend.y = y;
- _thd.next_drawstyle = b;
+ TileIndex start_tile = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
+ TileIndex end_tile = TileVirtXY(_thd.selend.x, _thd.selend.y);
+
+ HighLightStyle ret = HT_POLY |
+ (HighLightStyle)TrackdirToTrack(best_trackdir) | // cast TRACK_XXX to HT_DIR_XXX
+ (start_tile == end_tile ? HT_RAIL : HT_LINE); // one tile case or multitile selection
+
+ ShowLengthMeasurement(ret, start_tile, end_tile, TCC_HOVER, true);
+ return ret;
}
/**
@@ -2769,27 +2997,12 @@ calc_heightdiff_single_direction:;
x = sx + Clamp(x - sx, -limit, limit);
y = sy + Clamp(y - sy, -limit, limit);
}
- if (_settings_client.gui.measure_tooltip) {
- TileIndex t0 = TileVirtXY(sx, sy);
- TileIndex t1 = TileVirtXY(x, y);
- uint distance = DistanceManhattan(t0, t1) + 1;
- byte index = 0;
- uint64 params[2];
-
- if (distance != 1) {
- /* With current code passing a HT_LINE style to calculate the height
- * difference is enough. However if/when a point-tool is created
- * with this method, function should be called with new_style (below)
- * instead of HT_LINE | style case HT_POINT is handled specially
- * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
- int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
-
- params[index++] = distance;
- if (heightdiff != 0) params[index++] = heightdiff;
- }
-
- ShowMeasurementTooltips(measure_strings_length[index], index, params);
- }
+ /* With current code passing a HT_LINE style to calculate the height
+ * difference is enough. However if/when a point-tool is created
+ * with this method, function should be called with new_style (below)
+ * instead of HT_LINE | style case HT_POINT is handled specially
+ * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
+ ShowLengthMeasurement(HT_LINE | style, TileVirtXY(sx, sy), TileVirtXY(x, y));
break;
case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
@@ -2900,7 +3113,7 @@ EventState VpHandlePlaceSizingDrag()
} else if (_thd.select_method & VPM_SIGNALDIRS) {
_thd.place_mode = HT_RECT | others;
} else if (_thd.select_method & VPM_RAILDIRS) {
- _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
+ _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS ? _thd.next_drawstyle : HT_RAIL) | others;
} else {
_thd.place_mode = HT_POINT | others;
}
@@ -2966,3 +3179,85 @@ void ResetObjectToPlace()
{
SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
}
+
+void RemoveConnectedEndpointDirs(RailTrackEndpoint *a, const RailTrackEndpoint &b)
+{
+ if (IsValidTile(a->tile) && IsValidTile(b.tile)) {
+ for (DiagDirection test_dir = DIAGDIR_BEGIN; test_dir < DIAGDIR_END; test_dir++) {
+ TrackdirBits test_trackdirs = DiagdirReachesTrackdirs(test_dir);
+ if (test_trackdirs & a->dirs) {
+ if (a->tile == b.tile) {
+ if (b.dirs & test_trackdirs) a->dirs &= ~test_trackdirs;
+ } else if (a->tile - TileOffsByDiagDir(test_dir) == b.tile) {
+ if (b.dirs & DiagdirReachesTrackdirs(ReverseDiagDir(test_dir))) a->dirs &= ~test_trackdirs;
+ }
+ if (a->dirs == TRACKDIR_BIT_NONE) {
+ a->tile = INVALID_TILE;
+ return;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Store the position of lastly built rail track; for highlighting purposes.
+ *
+ * In "polyline" highlighting mode, the stored end point of the track
+ * will be used as the start point of a being highlighted track.
+ *
+ * @param start_tile tile where the track starts
+ * @param end_tile tile where the track ends
+ * @param start_track track piece on the start_tile
+ * @param bidirectional whether to allow to highlight next track in any direction; otherwise new track will have to fallow the stored one (usefull when placing tunnels and bridges)
+ */
+void StoreRailPlacementEndpoints(TileIndex start_tile, TileIndex end_tile, Track start_track, bool bidirectional)
+{
+ const uint NUM_ENDPOINTS = lengthof(_rail_track_endpoints);
+
+ RailTrackEndpoint new_endpoints[NUM_ENDPOINTS] = {
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ };
+
+ if (start_tile != INVALID_TILE && end_tile != INVALID_TILE) {
+ /* calculate trackdirs at booth ends of the track (pointing toward track middle) */
+ Trackdir start_trackdir = TrackToTrackdir(start_track);
+ Trackdir end_trackdir = ReverseTrackdir(start_trackdir);
+ if (start_tile != end_tile) { // multi-tile case
+ /* determine proper direction (toward track middle) */
+ uint distance = DistanceManhattan(start_tile, end_tile);
+ if (distance < DistanceManhattan(TileAddByDiagDir(start_tile, TrackdirToExitdir(start_trackdir)), end_tile)) {
+ Swap(start_trackdir, end_trackdir);
+ }
+ /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */
+ if (distance % 2 != 0) end_trackdir = NextTrackdir(end_trackdir);
+ }
+
+ /* compute new endpoints */
+ DiagDirection start_exit_dir = TrackdirToExitdir(ReverseTrackdir(start_trackdir));
+ DiagDirection end_exit_dir = TrackdirToExitdir(ReverseTrackdir(end_trackdir));
+ new_endpoints[0].tile = TileAddByDiagDir(start_tile, start_exit_dir);
+ new_endpoints[0].dirs = DiagdirReachesTrackdirs(start_exit_dir);
+ new_endpoints[1].tile = TileAddByDiagDir(end_tile, end_exit_dir);
+ new_endpoints[1].dirs = DiagdirReachesTrackdirs(end_exit_dir);
+ if (bidirectional) {
+ new_endpoints[2].tile = start_tile;
+ new_endpoints[2].dirs = DiagdirReachesTrackdirs(ReverseDiagDir(start_exit_dir));
+ new_endpoints[3].tile = end_tile;
+ new_endpoints[3].dirs = DiagdirReachesTrackdirs(ReverseDiagDir(end_exit_dir));
+ }
+
+ /* exclude all endpoints stored previously */
+ for (uint i = 0; i < NUM_ENDPOINTS; i++) {
+ for (uint j = 0; j < NUM_ENDPOINTS; j++) {
+ RemoveConnectedEndpointDirs(&new_endpoints[i], _rail_track_endpoints[j]);
+ }
+ }
+ }
+
+ /* store endpoints */
+ MemCpyT(_rail_track_endpoints, new_endpoints, NUM_ENDPOINTS);
+}