make selection box sub-tile aware
This commit is contained in:
@@ -17,6 +17,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WorldCoordinates.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WorldCamera.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FloatingPanelPlacement.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionBox.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildModeController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ControlAction.h
|
||||
|
||||
34
src/lib/core/SelectionBox.h
Normal file
34
src/lib/core/SelectionBox.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRectF>
|
||||
#include <QVector2D>
|
||||
|
||||
// The coverage rules of a selection box (REQ-UI-MULTI-SELECT, REQ-BLD-DECONSTRUCT-BOX).
|
||||
//
|
||||
// The box is a rectangle in world coordinates — tiles as the unit, but fractional,
|
||||
// because the drag follows the mouse and is not snapped to the tile grid. The two
|
||||
// rules below are the whole of what "covered by the box" means; they live here so the
|
||||
// building query and the entity queries answer it identically.
|
||||
//
|
||||
// Both callers pass a normalized rectangle: neither rule normalizes on its own.
|
||||
|
||||
// Whether the box overlaps the unit square of `tile` — the rule for anything that
|
||||
// occupies whole tiles (buildings, construction sites, defence station bodies). The
|
||||
// comparisons are inclusive, so a box that only grazes the tile's edge still covers
|
||||
// it, and a box with no area covers the tile it lies on.
|
||||
inline bool boxCoversTile(const QRectF& worldBox, QPoint tile)
|
||||
{
|
||||
return worldBox.left() <= static_cast<qreal>(tile.x()) + 1.0
|
||||
&& worldBox.right() >= static_cast<qreal>(tile.x())
|
||||
&& worldBox.top() <= static_cast<qreal>(tile.y()) + 1.0
|
||||
&& worldBox.bottom() >= static_cast<qreal>(tile.y());
|
||||
}
|
||||
|
||||
// Whether the box contains `worldPos` — the rule for anything that has a position
|
||||
// rather than a footprint (ships, debris). Their centre is what the box must enclose,
|
||||
// so that what the rectangle visibly holds is what the drag selects.
|
||||
inline bool boxCoversPoint(const QRectF& worldBox, QVector2D worldPos)
|
||||
{
|
||||
return worldBox.contains(QPointF(worldPos.x(), worldPos.y()));
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cmath>
|
||||
|
||||
#include "EntityAdmin.h"
|
||||
#include "SelectionBox.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "DebrisComponent.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
@@ -82,45 +83,29 @@ entt::entity debrisAtWorldPos(EntityAdmin& admin, QVector2D worldPos)
|
||||
return bestDebris;
|
||||
}
|
||||
|
||||
std::vector<entt::entity> debrisInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB)
|
||||
std::vector<entt::entity> debrisInBox(EntityAdmin& admin, const QRectF& worldBox)
|
||||
{
|
||||
const int minX = std::min(tileA.x(), tileB.x());
|
||||
const int maxX = std::max(tileA.x(), tileB.x());
|
||||
const int minY = std::min(tileA.y(), tileB.y());
|
||||
const int maxY = std::max(tileA.y(), tileB.y());
|
||||
|
||||
std::vector<entt::entity> result;
|
||||
admin.forEach<DebrisComponent, PositionComponent>(
|
||||
[&](entt::entity entity, const DebrisComponent& /*sd*/, const PositionComponent& pos)
|
||||
{
|
||||
const int tileX = static_cast<int>(std::floor(pos.value.x()));
|
||||
const int tileY = static_cast<int>(std::floor(pos.value.y()));
|
||||
if (tileX >= minX && tileX <= maxX && tileY >= minY && tileY <= maxY)
|
||||
{
|
||||
result.push_back(entity);
|
||||
}
|
||||
if (boxCoversPoint(worldBox, pos.value)) { result.push_back(entity); }
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<entt::entity> actorsInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB)
|
||||
std::vector<entt::entity> actorsInBox(EntityAdmin& admin, const QRectF& worldBox)
|
||||
{
|
||||
const int minX = std::min(tileA.x(), tileB.x());
|
||||
const int maxX = std::max(tileA.x(), tileB.x());
|
||||
const int minY = std::min(tileA.y(), tileB.y());
|
||||
const int maxY = std::max(tileA.y(), tileB.y());
|
||||
|
||||
std::vector<entt::entity> result;
|
||||
|
||||
// Stations: included when any occupied body cell lies in the box.
|
||||
// Stations occupy whole tiles: included when the box overlaps any occupied cell.
|
||||
admin.forEach<StationBodyComponent, HealthComponent>(
|
||||
[&](entt::entity entity, const StationBodyComponent& sb, const HealthComponent& h)
|
||||
{
|
||||
if (h.hp <= 0.0f) { return; }
|
||||
for (const QPoint& cell : sb.bodyCells)
|
||||
{
|
||||
if (cell.x() >= minX && cell.x() <= maxX
|
||||
&& cell.y() >= minY && cell.y() <= maxY)
|
||||
if (boxCoversTile(worldBox, cell))
|
||||
{
|
||||
result.push_back(entity);
|
||||
return;
|
||||
@@ -128,19 +113,15 @@ std::vector<entt::entity> actorsInBox(EntityAdmin& admin, QPoint tileA, QPoint t
|
||||
}
|
||||
});
|
||||
|
||||
// Ships: included when the floored position tile lies in the box. Requiring
|
||||
// ShipIdentityComponent excludes the HQ proxy and any station bodies.
|
||||
// Ships have a position rather than a footprint: included when the box contains
|
||||
// that position. Requiring ShipIdentityComponent excludes the HQ proxy and any
|
||||
// station bodies.
|
||||
admin.forEach<ShipIdentityComponent, PositionComponent, HealthComponent>(
|
||||
[&](entt::entity entity, const ShipIdentityComponent& /*id*/,
|
||||
const PositionComponent& pos, const HealthComponent& h)
|
||||
{
|
||||
if (h.hp <= 0.0f) { return; }
|
||||
const int tileX = static_cast<int>(std::floor(pos.value.x()));
|
||||
const int tileY = static_cast<int>(std::floor(pos.value.y()));
|
||||
if (tileX >= minX && tileX <= maxX && tileY >= minY && tileY <= maxY)
|
||||
{
|
||||
result.push_back(entity);
|
||||
}
|
||||
if (boxCoversPoint(worldBox, pos.value)) { result.push_back(entity); }
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRectF>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
@@ -16,13 +17,13 @@ entt::entity entityAtWorldPos(EntityAdmin& admin, QVector2D worldPos);
|
||||
// after actors: entityAtWorldPos never returns debris (debris has no HealthComponent).
|
||||
entt::entity debrisAtWorldPos(EntityAdmin& admin, QVector2D worldPos);
|
||||
|
||||
// Returns every piece of debris whose position falls within the inclusive tile rectangle
|
||||
// spanned by tileA and tileB, in any corner order (REQ-UI-DEBRIS-MULTI-SELECT).
|
||||
std::vector<entt::entity> debrisInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB);
|
||||
// Returns every piece of debris the selection box covers — that is, whose position it
|
||||
// contains, per boxCoversPoint (REQ-UI-DEBRIS-MULTI-SELECT). `worldBox` is in world
|
||||
// coordinates and normalized; it is not snapped to tiles.
|
||||
std::vector<entt::entity> debrisInBox(EntityAdmin& admin, const QRectF& worldBox);
|
||||
|
||||
// Returns every living actor (ship or defence station, player or enemy) that falls
|
||||
// within the inclusive tile rectangle spanned by tileA and tileB, in any corner order
|
||||
// (REQ-UI-MULTI-SELECT, REQ-UI-ENTITY-CLICK-SELECT). A ship is included when its floored
|
||||
// position tile lies in the box; a station is included when any of its body cells does.
|
||||
// Dead actors (hp <= 0) and the HQ proxy are excluded.
|
||||
std::vector<entt::entity> actorsInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB);
|
||||
// Returns every living actor (ship or defence station, player or enemy) the selection
|
||||
// box covers (REQ-UI-MULTI-SELECT, REQ-UI-ENTITY-CLICK-SELECT): a ship when the box
|
||||
// contains its position, a station when the box overlaps any of its body cells — the
|
||||
// two rules of SelectionBox.h. Dead actors (hp <= 0) and the HQ proxy are excluded.
|
||||
std::vector<entt::entity> actorsInBox(EntityAdmin& admin, const QRectF& worldBox);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "PortGeometry.h"
|
||||
#include "ProductionRules.h"
|
||||
#include "SelectionBox.h"
|
||||
#include "SurfaceMask.h"
|
||||
|
||||
#include "Item.h"
|
||||
@@ -185,22 +186,13 @@ getSiteSplitterInfo(const FactoryState& state, const GameConfig& config, Buildin
|
||||
|
||||
|
||||
std::vector<BuildingId> buildingsInBox(const FactoryState& state,
|
||||
QPoint cornerA, QPoint cornerB)
|
||||
const QRectF& worldBox)
|
||||
{
|
||||
const int x0 = std::min(cornerA.x(), cornerB.x());
|
||||
const int y0 = std::min(cornerA.y(), cornerB.y());
|
||||
const int x1 = std::max(cornerA.x(), cornerB.x());
|
||||
const int y1 = std::max(cornerA.y(), cornerB.y());
|
||||
|
||||
const auto covers = [&](const std::vector<QPoint>& bodyCells)
|
||||
{
|
||||
for (const QPoint& cell : bodyCells)
|
||||
{
|
||||
if (cell.x() >= x0 && cell.x() <= x1
|
||||
&& cell.y() >= y0 && cell.y() <= y1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (boxCoversTile(worldBox, cell)) { return true; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRectF>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "Building.h"
|
||||
@@ -71,11 +72,12 @@ std::optional<BeltSystem::SplitterInfo> getSiteSplitterInfo(const FactoryState&
|
||||
const GameConfig& config,
|
||||
BuildingId id);
|
||||
|
||||
// Ids of all buildings and construction sites whose footprint intersects the tile
|
||||
// box spanned by the two (unordered) corner tiles (REQ-UI-MULTI-SELECT,
|
||||
// REQ-BLD-DECONSTRUCT-BOX).
|
||||
// Ids of all buildings and construction sites the selection box covers — those with
|
||||
// a body cell the box overlaps, per boxCoversTile (REQ-UI-MULTI-SELECT,
|
||||
// REQ-BLD-DECONSTRUCT-BOX). `worldBox` is in world coordinates and normalized; it is
|
||||
// not snapped to tiles.
|
||||
std::vector<BuildingId> buildingsInBox(const FactoryState& state,
|
||||
QPoint cornerA, QPoint cornerB);
|
||||
const QRectF& worldBox);
|
||||
|
||||
// Every tunnel entry and exit, built or still a construction site, indexed by its
|
||||
// single-cell tile. Shared by the placement preview and the selection highlight.
|
||||
|
||||
Reference in New Issue
Block a user