Files
dota_factory/src/ui/SelectionBounds.cpp

116 lines
4.0 KiB
C++

#include "SelectionBounds.h"
#include "Building.h"
#include "DebrisComponent.h"
#include "EntityAdmin.h"
#include "FactoryQueries.h"
#include "PositionComponent.h"
#include "Simulation.h"
#include "StationBodyComponent.h"
#include "WorldCoordinates.h"
#include "WorldPrimitives.h"
namespace
{
// The widget rectangle of a footprint anchored at a tile, both kinds of body being
// described the same way.
QRectF getFootprintWidgetRect(const WorldCoordinates& coordinates, QPoint anchor,
QSize footprint)
{
const QPointF topLeft = coordinates.tileToWidget(anchor);
return QRectF(topLeft.x(), topLeft.y(),
footprint.width() * static_cast<qreal>(coordinates.getTilePx()),
footprint.height() * static_cast<qreal>(coordinates.getTilePx()));
}
} // namespace
std::optional<QRectF> getBuildingWidgetRect(const FactoryState& state,
const WorldCoordinates& coordinates,
BuildingId id)
{
if (const Building* building = findBuilding(state, id))
{
return getFootprintWidgetRect(coordinates, building->anchor, building->footprint);
}
if (const ConstructionSite* site = findSite(state, id))
{
return getFootprintWidgetRect(coordinates, site->anchor, site->footprint);
}
return std::nullopt;
}
std::optional<QRectF> getActorWidgetRect(const EntityAdmin& admin,
const WorldCoordinates& coordinates,
entt::entity actor)
{
if (!admin.isValid(actor))
{
return std::nullopt;
}
if (admin.hasAll<StationBodyComponent>(actor))
{
const StationBodyComponent& body = admin.get<StationBodyComponent>(actor);
return getFootprintWidgetRect(coordinates, body.anchor, body.footprint);
}
if (admin.hasAll<PositionComponent>(actor))
{
// A ship is a triangle about its center; the square its longest extent fits in
// is what the panel is placed beside.
const QPointF center =
coordinates.worldToWidget(admin.get<PositionComponent>(actor).value);
const qreal extent = static_cast<qreal>(getShipForwardExtentPx(coordinates));
return QRectF(center.x() - extent, center.y() - extent,
2.0 * extent, 2.0 * extent);
}
return std::nullopt;
}
std::optional<QRectF> getDebrisWidgetRect(const EntityAdmin& admin,
const WorldCoordinates& coordinates,
entt::entity debris)
{
if (!admin.isValid(debris) || !admin.hasAll<PositionComponent, DebrisComponent>(debris))
{
return std::nullopt;
}
const QPointF center =
coordinates.worldToWidget(admin.get<PositionComponent>(debris).value);
const qreal radius = static_cast<qreal>(getDebrisRadiusPx(coordinates));
return QRectF(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius);
}
QRect getSelectionWidgetRect(const Simulation& sim, const WorldCoordinates& coordinates,
const std::vector<BuildingId>& buildings,
const std::vector<entt::entity>& actors,
const std::vector<entt::entity>& debris)
{
QRectF bounds;
// A null rect unites to nothing of its own, so the first object found sets the box
// and every later one grows it.
auto add = [&bounds](const std::optional<QRectF>& rect)
{
if (rect.has_value())
{
bounds = bounds.isNull() ? *rect : bounds.united(*rect);
}
};
for (BuildingId id : buildings)
{
add(getBuildingWidgetRect(sim.getFactoryState(), coordinates, id));
}
for (entt::entity actor : actors)
{
add(getActorWidgetRect(sim.getAdmin(), coordinates, actor));
}
for (entt::entity piece : debris)
{
add(getDebrisWidgetRect(sim.getAdmin(), coordinates, piece));
}
return bounds.isNull() ? QRect() : bounds.toAlignedRect();
}