156 lines
5.5 KiB
C++
156 lines
5.5 KiB
C++
#include "TunnelCompletion.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
QPoint stepTile(QPoint tile, Rotation dir)
|
|
{
|
|
switch (dir)
|
|
{
|
|
case Rotation::North: return {tile.x(), tile.y() - 1};
|
|
case Rotation::East: return {tile.x() + 1, tile.y() };
|
|
case Rotation::South: return {tile.x(), tile.y() + 1};
|
|
case Rotation::West: return {tile.x() - 1, tile.y() };
|
|
}
|
|
return tile;
|
|
}
|
|
|
|
Rotation oppositeRotation(Rotation dir)
|
|
{
|
|
switch (dir)
|
|
{
|
|
case Rotation::North: return Rotation::South;
|
|
case Rotation::East: return Rotation::West;
|
|
case Rotation::South: return Rotation::North;
|
|
case Rotation::West: return Rotation::East;
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
float tileCenterDistanceSq(QPoint tile, QVector2D cursorWorldPos)
|
|
{
|
|
const QVector2D center(static_cast<float>(tile.x()) + 0.5f,
|
|
static_cast<float>(tile.y()) + 0.5f);
|
|
return (center - cursorWorldPos).lengthSquared();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::optional<QPoint> firstTunnelFacing(const TunnelLookup& lookup, QPoint start,
|
|
Rotation stepDir, Rotation targetFacing,
|
|
int maxDistance)
|
|
{
|
|
QPoint probe = start;
|
|
for (int distance = 1; distance <= maxDistance; ++distance)
|
|
{
|
|
probe = stepTile(probe, stepDir);
|
|
const std::optional<TunnelTileInfo> info = lookup(probe);
|
|
if (info.has_value() && info->rotation == targetFacing)
|
|
{
|
|
return probe;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
TunnelCompletion resolveTunnelCompletion(const TunnelLookup& lookup, QPoint hoverTile,
|
|
Rotation rotation, int maxDistance,
|
|
QVector2D cursorWorldPos)
|
|
{
|
|
// Entry-completion: a hypothetical entry at the hovered tile searches ahead (its
|
|
// facing direction) for a same-direction exit to pair with. The first
|
|
// same-direction tunnel ahead completes the entry only if it is an exit.
|
|
std::optional<QPoint> exitPartner;
|
|
if (const std::optional<QPoint> ahead =
|
|
firstTunnelFacing(lookup, hoverTile, rotation, rotation, maxDistance);
|
|
ahead.has_value())
|
|
{
|
|
const std::optional<TunnelTileInfo> info = lookup(*ahead);
|
|
if (info.has_value() && info->type == BuildingType::TunnelExit)
|
|
{
|
|
exitPartner = ahead;
|
|
}
|
|
}
|
|
|
|
// Exit-completion: a hypothetical exit at the hovered tile pairs with an existing
|
|
// entry behind it — one whose forward search (its facing direction) reaches the
|
|
// hovered tile as its first same-direction tunnel. Scanning backwards, the first
|
|
// same-direction tunnel completes the exit only if it is an entry.
|
|
std::optional<QPoint> entryPartner;
|
|
if (const std::optional<QPoint> behind =
|
|
firstTunnelFacing(lookup, hoverTile, oppositeRotation(rotation), rotation,
|
|
maxDistance);
|
|
behind.has_value())
|
|
{
|
|
const std::optional<TunnelTileInfo> info = lookup(*behind);
|
|
if (info.has_value() && info->type == BuildingType::TunnelEntry)
|
|
{
|
|
entryPartner = behind;
|
|
}
|
|
}
|
|
|
|
// Default: a TunnelEntry with no partner.
|
|
if (!entryPartner.has_value() && !exitPartner.has_value())
|
|
{
|
|
return TunnelCompletion{BuildingType::TunnelEntry, std::nullopt};
|
|
}
|
|
if (entryPartner.has_value() && !exitPartner.has_value())
|
|
{
|
|
return TunnelCompletion{BuildingType::TunnelExit, entryPartner};
|
|
}
|
|
if (exitPartner.has_value() && !entryPartner.has_value())
|
|
{
|
|
return TunnelCompletion{BuildingType::TunnelEntry, exitPartner};
|
|
}
|
|
|
|
// Both apply: complete whichever existing partner is closer to the sub-tile
|
|
// cursor position (REQ-BLD-TUNNEL-MODE).
|
|
const float entryDistanceSq = tileCenterDistanceSq(*entryPartner, cursorWorldPos);
|
|
const float exitDistanceSq = tileCenterDistanceSq(*exitPartner, cursorWorldPos);
|
|
if (entryDistanceSq <= exitDistanceSq)
|
|
{
|
|
return TunnelCompletion{BuildingType::TunnelExit, entryPartner};
|
|
}
|
|
return TunnelCompletion{BuildingType::TunnelEntry, exitPartner};
|
|
}
|
|
|
|
std::optional<QPoint> findTunnelPartner(const TunnelLookup& lookup, QPoint tile,
|
|
BuildingType type, Rotation rotation,
|
|
int maxDistance)
|
|
{
|
|
if (type == BuildingType::TunnelEntry)
|
|
{
|
|
// An entry pairs with the first same-direction tunnel ahead, if it is an exit.
|
|
const std::optional<QPoint> ahead =
|
|
firstTunnelFacing(lookup, tile, rotation, rotation, maxDistance);
|
|
if (ahead.has_value())
|
|
{
|
|
const std::optional<TunnelTileInfo> info = lookup(*ahead);
|
|
if (info.has_value() && info->type == BuildingType::TunnelExit)
|
|
{
|
|
return ahead;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (type == BuildingType::TunnelExit)
|
|
{
|
|
// An exit is claimed by the first same-direction tunnel behind it, if it is an
|
|
// entry (its forward search reaches this exit as its first same-direction tunnel).
|
|
const std::optional<QPoint> behind =
|
|
firstTunnelFacing(lookup, tile, oppositeRotation(rotation), rotation, maxDistance);
|
|
if (behind.has_value())
|
|
{
|
|
const std::optional<TunnelTileInfo> info = lookup(*behind);
|
|
if (info.has_value() && info->type == BuildingType::TunnelEntry)
|
|
{
|
|
return behind;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|