1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00
Files
vcmi/mapeditor/scenelayer.cpp
2025-11-06 21:40:44 +01:00

863 lines
19 KiB
C++

/*
* scenelayer.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "scenelayer.h"
#include "mainwindow.h"
#include "../lib/mapping/CMapEditManager.h"
#include "../lib/mapping/CMap.h"
#include "inspector/inspector.h"
#include "mapview.h"
#include "mapcontroller.h"
AbstractLayer::AbstractLayer(MapSceneBase * s): scene(s)
{
}
void AbstractLayer::initialize(MapController & controller)
{
map = controller.map();
handler = controller.mapHandler();
}
void AbstractLayer::show(bool show)
{
isShown = show;
redraw();
}
int AbstractLayer::mapWidthPx() const
{
return map ? map->width * tileSize : 0;
}
int AbstractLayer::mapHeightPx() const
{
return map ? map->height * tileSize : 0;
}
int AbstractLayer::toInt(double value) const
{
return static_cast<int>(std::round(value)); // is rounded explicitly in order to avoid rounding down unprecise double values
}
AbstractFixedLayer::AbstractFixedLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void AbstractFixedLayer::redraw()
{
if(item)
{
if(pixmap && isShown)
item->setPixmap(*pixmap);
else
item->setPixmap(emptyPixmap);
}
else
{
if(pixmap && isShown)
item.reset(scene->addPixmap(*pixmap));
else
item.reset(scene->addPixmap(emptyPixmap));
}
}
AbstractViewportLayer::AbstractViewportLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void AbstractViewportLayer::createLayer()
{
QList<QGraphicsItem *>emptyList;
items.reset(scene->createItemGroup(emptyList));
}
void AbstractViewportLayer::setViewport(const QRectF & viewPort)
{
if(!map)
return;
if (items->boundingRect().contains(viewPort))
return;
std::vector<QGraphicsItem *> outOfScreenSectors;
for (QGraphicsItem * sector : getAllSectors())
{
if (!viewPort.intersects(sector->sceneBoundingRect()))
outOfScreenSectors.push_back(sector);
}
for (QGraphicsItem * sector : outOfScreenSectors)
{
removeSector(sector);
}
std::vector<QRectF> newAreas;
int left = toInt(viewPort.left());
int right = toInt(viewPort.right());
int top = toInt(viewPort.top());
int bottom = toInt(viewPort.bottom());
int startX = left - (left % sectorSize);
int limitX = std::min(right + (sectorSize - right % sectorSize), mapWidthPx());
int startY = top - (top % sectorSize);
int limitY = std::min(bottom + (sectorSize - bottom % sectorSize), mapHeightPx());
for (int x = startX; x < limitX; x += sectorSize)
{
for (int y = startY; y < limitY; y += sectorSize)
{
int width = x + sectorSize < limitX ? sectorSize : limitX - x;
int height = y + sectorSize < limitY ? sectorSize : limitY - y;
QRectF area(x, y, width, height);
if (!items->boundingRect().intersects(area))
newAreas.emplace_back(area);
}
}
for(QRectF newSection : newAreas)
{
QGraphicsItem * sector = draw(newSection);
if (sector)
addSector(sector);
}
}
void AbstractViewportLayer::update()
{
redraw();
}
void AbstractViewportLayer::redraw()
{
std::set<QGraphicsItem *> allSectors;
for (auto * sector : getAllSectors())
allSectors.insert(sector);
redrawSectors(allSectors);
}
void AbstractViewportLayer::redraw(const std::vector<int3> & tiles)
{
std::set<QGraphicsItem *> sectorsToRedraw = getContainingSectors(tiles);
redrawSectors(sectorsToRedraw);
}
void AbstractViewportLayer::redrawWithSurroundingTiles(const std::vector<int3> & tiles)
{
int maxX = 0;
int maxY = 0;
int minX = INT_MAX;
int minY = INT_MAX;
for (const int3 tile : tiles)
{
maxX = std::max(tile.x, maxX);
maxY = std::max(tile.y, maxY);
minX = std::min(tile.x, minX);
minY = std::min(tile.y, minY);
}
QRectF bounds((minX - 2) * tileSize, (minY - 2) * tileSize, (maxX - minX + 4) * tileSize, (maxY - minY + 4) * tileSize); //tiles start with 1, QRectF from 0
redraw({bounds});
}
void AbstractViewportLayer::redraw(const std::set<CGObjectInstance *> & objects)
{
std::vector<QRectF> areas;
areas.reserve(objects.size());
for (const CGObjectInstance * object : objects)
{
areas.push_back(getObjectArea(object));
}
redraw(areas);
}
void AbstractViewportLayer::redraw(const std::vector<QRectF> & areas)
{
std::set<QGraphicsItem *> intersectingSectors;
for (QGraphicsItem * existingSector : getAllSectors())
{
for (auto area : areas)
{
if (existingSector->sceneBoundingRect().intersects(area))
{
intersectingSectors.insert(existingSector);
}
}
}
redrawSectors(intersectingSectors);
}
QRectF AbstractViewportLayer::getObjectArea(const CGObjectInstance * object) const
{
auto pos = object->pos;
int x = ((pos.x + 1) * tileSize) - (object->getWidth() * tileSize); //Qt set 0,0 point on the top right corner, CGObjectInstance on the bottom left
int y = ((pos.y + 1) * tileSize) - (object->getHeight() * tileSize);
QRectF objectArea(x, y, object->getWidth() * tileSize, object->getHeight() * tileSize);
return objectArea;
}
void AbstractViewportLayer::addSector(QGraphicsItem * sector)
{
items->addToGroup(sector);
}
void AbstractViewportLayer::removeSector(QGraphicsItem * sector)
{
items->removeFromGroup(sector);
delete sector;
}
void AbstractViewportLayer::redrawSectors(std::set<QGraphicsItem *> & sectors)
{
std::set<QGraphicsItem *> sectorsToRemove;
for (QGraphicsItem * existingSectors : getAllSectors())
{
for (QGraphicsItem * sector : sectors)
{
if (existingSectors->sceneBoundingRect().contains(sector->sceneBoundingRect()))
sectorsToRemove.insert(existingSectors);
}
}
for (QGraphicsItem * sectorToRemove : sectorsToRemove)
{
addSector(draw(sectorToRemove->sceneBoundingRect()));
removeSector(sectorToRemove);
}
}
const QList<QGraphicsItem *> AbstractViewportLayer::getAllSectors() const //returning const is necessary to avoid "range-loop might detach Qt container" problem
{
QList<QGraphicsItem *> emptyList;
return items ? items->childItems() : emptyList;
}
std::set<QGraphicsItem *> AbstractViewportLayer::getContainingSectors(const std::vector<int3> & tiles) const
{
std::set<QGraphicsItem *> result;
for (QGraphicsItem * existingSector : getAllSectors()) {
for (const int3 tile : tiles)
{
if (existingSector->sceneBoundingRect().contains(QPointF(tile.x * tileSize, tile.y * tileSize)))
{
result.insert(existingSector);
break;
}
}
}
return result;
}
std::set<QGraphicsItem *> AbstractViewportLayer::getIntersectingSectors(const std::vector<QRectF> & areas) const
{
std::set<QGraphicsItem *> result;
for (QGraphicsItem * existingSector : getAllSectors()) {
for (QRectF area : areas)
{
if (existingSector->sceneBoundingRect().intersects(area))
{
result.insert(existingSector);
}
}
}
return result;
}
EmptyLayer::EmptyLayer(MapSceneBase * s): AbstractFixedLayer(s)
{
isShown = true;
}
void EmptyLayer::update()
{
if(!map)
return;
pixmap = std::make_unique<QPixmap>(map->width * 32, map->height * 32);
redraw();
}
GridLayer::GridLayer(MapSceneBase * s): AbstractViewportLayer(s)
{
}
QGraphicsItem * GridLayer::draw(const QRectF & section)
{
QPixmap pixmap(toInt(section.width()), toInt(section.height()));
pixmap.fill(Qt::transparent);
if (isShown)
{
QPainter painter(&pixmap);
painter.setPen(QColor(0, 0, 0, 190));
for(int j = 0; j <= pixmap.height(); j += tileSize)
{
painter.drawLine(0, j, pixmap.width(), j);
}
for(int i = 0; i <= pixmap.width(); i += tileSize)
{
painter.drawLine(i, 0, i, pixmap.height());
}
}
QGraphicsItem * result = scene->addPixmap(pixmap);
result->setPos(section.x(), section.y());
return result;
}
PassabilityLayer::PassabilityLayer(MapSceneBase * s): AbstractViewportLayer(s)
{
}
QGraphicsItem * PassabilityLayer::draw(const QRectF & section)
{
QPixmap pixmap(toInt(section.width()), toInt(section.height()));
pixmap.fill(Qt::transparent);
if(isShown)
{
QPainter painter(&pixmap);
for(int j = 0; j <= pixmap.height(); j += tileSize)
{
for(int i = 0; i < pixmap.width(); i += tileSize)
{
auto tl = map->getTile(int3(toInt(section.x())/tileSize + i/tileSize, toInt(section.y())/tileSize + j/tileSize, scene->level));
if(tl.blocked() || tl.visitable())
{
painter.fillRect(i, j, 31, 31, tl.visitable() ? QColor(200, 200, 0, 64) : QColor(255, 0, 0, 64));
}
}
}
}
QGraphicsItem * result = scene->addPixmap(pixmap);
result->setPos(section.x(), section.y());
return result;
}
ObjectPickerLayer::ObjectPickerLayer(MapSceneBase * s): AbstractViewportLayer(s)
{
}
void ObjectPickerLayer::highlight(const std::function<bool(const CGObjectInstance *)> & predicate)
{
if(!map)
return;
for(int j = 0; j < map->height; ++j)
{
for(int i = 0; i < map->width; ++i)
{
auto tl = map->getTile(int3(i, j, scene->level));
ObjectInstanceID objID = tl.topVisitableObj();
if(!objID.hasValue() && !tl.blockingObjects.empty())
objID = tl.blockingObjects.front();
if (objID.hasValue())
{
const CGObjectInstance * obj = map->getObject(objID);
if(obj && predicate(obj))
possibleObjects.insert(obj);
}
}
}
isActive = true;
}
bool ObjectPickerLayer::isVisible() const
{
return isShown && isActive;
}
void ObjectPickerLayer::clear()
{
possibleObjects.clear();
isActive = false;
}
QGraphicsItem * ObjectPickerLayer::draw(const QRectF & section)
{
int offsetX = toInt(section.x());
int offsetY = toInt(section.y());
QPixmap pixmap(toInt(section.width()), toInt(section.height()));
pixmap.fill(Qt::transparent);
if(isVisible())
pixmap.fill(QColor(255, 255, 255, 128));
QPainter painter(&pixmap);
painter.setCompositionMode(QPainter::CompositionMode_Source);
for(const auto * obj : possibleObjects)
{
if(obj->pos.z != scene->level)
continue;
for(const auto & pos : obj->getBlockedPos())
painter.fillRect(pos.x * tileSize - offsetX, pos.y * tileSize - offsetY, tileSize, tileSize, QColor(255, 211, 0, 64));
}
QGraphicsItem * result = scene->addPixmap(pixmap);
result->setPos(section.x(), section.y());
return result;
}
void ObjectPickerLayer::select(const CGObjectInstance * obj)
{
if(obj && possibleObjects.count(obj))
{
clear();
Q_EMIT selectionMade(obj);
}
}
void ObjectPickerLayer::discard()
{
clear();
Q_EMIT selectionMade(nullptr);
}
SelectionTerrainLayer::SelectionTerrainLayer(MapSceneBase * s): AbstractViewportLayer(s)
{
}
QGraphicsItem * SelectionTerrainLayer::draw(const QRectF & section)
{
int offsetX = toInt(section.x());
int offsetY = toInt(section.y());
QPixmap pixmap(toInt(section.width()), toInt(section.height()));
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setCompositionMode(QPainter::CompositionMode_Source);
for(const auto & t : area)
{
if(section.contains(t.x * tileSize, t.y * tileSize))
painter.fillRect(t.x * tileSize - offsetX, t.y * tileSize - offsetY, 31, 31, QColor(128, 128, 128, 96));
}
QGraphicsPixmapItem * result = scene->addPixmap(pixmap);
result->setPos(section.x(), section.y());
return result;
}
void SelectionTerrainLayer::select(const std::vector<int3> & tiles)
{
for (int3 tile : tiles)
{
if(!area.count(tile) && map->isInTheMap(tile))
area.insert(tile);
}
redraw(tiles);
onSelection();
}
void SelectionTerrainLayer::erase(const std::vector<int3> & tiles)
{
for (int3 tile : tiles)
{
if(area.count(tile))
{
area.erase(tile);
}
}
redraw(tiles);
onSelection();
}
void SelectionTerrainLayer::clear()
{
area.clear();
onSelection();
redraw();
}
const std::set<int3> & SelectionTerrainLayer::selection() const
{
return area;
}
void SelectionTerrainLayer::onSelection()
{
Q_EMIT selectionMade(!area.empty());
}
TerrainLayer::TerrainLayer(MapSceneBase * s): AbstractViewportLayer(s)
{
}
void TerrainLayer::redrawTerrain(const std::vector<int3> & tiles)
{
redrawWithSurroundingTiles(tiles);
}
QGraphicsItem * TerrainLayer::draw(const QRectF & section)
{
int left = toInt(section.left());
int right = toInt(section.right());
int top = toInt(section.top());
int bottom = toInt(section.bottom());
QPixmap pixmap(toInt(section.width()), toInt(section.height()));
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
QPointF offset = section.topLeft();
for(int x = left/tileSize; x < right/tileSize; ++x)
{
for(int y = top/tileSize; y < bottom/tileSize; ++y)
{
handler->drawTerrainTile(painter, x, y, scene->level, offset);
handler->drawRiver(painter, x, y, scene->level, offset);
handler->drawRoad(painter, x, y, scene->level, offset);
}
}
QGraphicsPixmapItem * result = scene->addPixmap(pixmap);
result->setPos(section.x(), section.y());
return result;
}
ObjectsLayer::ObjectsLayer(MapSceneBase * s): AbstractViewportLayer(s)
{
}
QGraphicsItem * ObjectsLayer::draw(const QRectF & section)
{
QPixmap pixmap(toInt(section.width()), toInt(section.height()));
pixmap.fill(Qt::transparent);
if (isShown)
{
QPainter painter(&pixmap);
handler->drawObjects(painter, section, scene->level, lockedObjects);
}
QGraphicsPixmapItem * result = scene->addPixmap(pixmap);
result->setPos(section.x(), section.y());
return result;
}
void ObjectsLayer::redrawObjects(const std::set<CGObjectInstance *> & objects)
{
redraw(objects);
}
void ObjectsLayer::setLockObject(const CGObjectInstance * object, bool lock)
{
if(lock)
lockedObjects.insert(object);
else
lockedObjects.erase(object);
QRectF area = getObjectArea(object);
redraw({area});
}
void ObjectsLayer::unlockAll()
{
lockedObjects.clear();
redraw();
}
SelectionObjectsLayer::SelectionObjectsLayer(MapSceneBase * s): AbstractViewportLayer(s), newObject(nullptr)
{
}
QGraphicsItem * SelectionObjectsLayer::draw(const QRectF & section)
{
QPixmap pixmap(toInt(section.width()), toInt(section.height()));
pixmap.fill(Qt::transparent);
if (isShown)
{
QPainter painter(&pixmap);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.setPen(Qt::white);
QPointF offset = section.topLeft();
for(auto * obj : selectedObjects)
{
auto objectArea = getObjectArea(obj);
if(obj != newObject.get() && section.intersects(objectArea))
{
auto pos = obj->anchorPos();
QRectF bbox(pos.x, pos.y, 1, 1);
for(const auto & t : obj->getBlockedPos())
{
QPointF topLeft(std::min(t.x * 1.0, bbox.topLeft().x()), std::min(t.y * 1.0, bbox.topLeft().y()));
bbox.setTopLeft(topLeft);
QPointF bottomRight(std::max(t.x * 1.0, bbox.bottomRight().x()), std::max(t.y * 1.0, bbox.bottomRight().y()));
bbox.setBottomRight(bottomRight);
}
//selection box's size was decreased by 1 px to get rid of a persistent bug
//with displaying a box on a border of two sectors. Bite me.
painter.setOpacity(1.0);
QRectF rect((bbox.x() * tileSize + 1) - offset.x(), (bbox.y() * tileSize + 1) - offset.y(), (bbox.width() * tileSize) - 2, (bbox.height() * tileSize) - 2);
painter.drawRect(rect);
}
if(selectionMode == SelectionMode::MOVEMENT && (shift.x() || shift.y()))
{
objectArea.moveTo(objectArea.topLeft() + (shift * tileSize));
if (section.intersects(objectArea))
{
painter.setOpacity(0.7);
auto newPos = QPoint(obj->anchorPos().x, obj->anchorPos().y) + shift;
handler->drawObjectAt(painter, obj, newPos.x(), newPos.y(), offset);
}
}
}
}
QGraphicsPixmapItem * result = scene->addPixmap(pixmap);
result->setPos(section.x(), section.y());
return result;
}
CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y, const CGObjectInstance * ignore) const
{
if(!map || !map->isInTheMap(int3(x, y, scene->level)))
return nullptr;
auto & objects = handler->getObjects(x, y, scene->level);
//visitable is most important
for(auto & object : objects)
{
if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj))
continue;
if(object.obj->visitableAt(int3(x, y, scene->level)))
{
return const_cast<CGObjectInstance*>(object.obj);
}
}
//if not visitable tile - try to get blocked
for(auto & object : objects)
{
if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj))
continue;
if(object.obj->blockingAt(int3(x, y, scene->level)))
{
return const_cast<CGObjectInstance*>(object.obj);
}
}
//finally, we can take any object
for(auto & object : objects)
{
if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj))
continue;
if(object.obj->coveringAt(int3(x, y, scene->level)))
{
return const_cast<CGObjectInstance*>(object.obj);
}
}
return nullptr;
}
void SelectionObjectsLayer::selectObjects(int x1, int y1, int x2, int y2)
{
if(!map)
return;
if(x1 > x2)
std::swap(x1, x2);
if(y1 > y2)
std::swap(y1, y2);
std::set<CGObjectInstance *> selectedObjects;
for(int j = y1; j < y2; ++j)
{
for(int i = x1; i < x2; ++i)
{
if(map->isInTheMap(int3(i, j, scene->level)))
{
for(auto & o : handler->getObjects(i, j, scene->level))
if(!lockedObjects.count(o.obj))
{
selectedObjects.insert(const_cast<CGObjectInstance*>(o.obj));
}
}
}
}
selectObjects(selectedObjects);
}
void SelectionObjectsLayer::selectObject(CGObjectInstance * obj)
{
selectedObjects.insert(obj);
onSelection();
redraw({obj});
}
void SelectionObjectsLayer::selectObjects(const std::set<CGObjectInstance *> & objs)
{
for (CGObjectInstance * obj : objs)
{
selectedObjects.insert(obj);
}
onSelection();
redraw(objs);
}
void SelectionObjectsLayer::deselectObject(CGObjectInstance * obj)
{
selectedObjects.erase(obj);
redraw({obj});
}
bool SelectionObjectsLayer::isSelected(const CGObjectInstance * obj) const
{
return selectedObjects.count(const_cast<CGObjectInstance*>(obj));
}
std::set<CGObjectInstance*> SelectionObjectsLayer::getSelection() const
{
return selectedObjects;
}
void SelectionObjectsLayer::clear()
{
selectedObjects.clear();
shift.setX(0);
shift.setY(0);
redraw();
}
void SelectionObjectsLayer::onSelection()
{
Q_EMIT selectionMade(!selectedObjects.empty());
}
void SelectionObjectsLayer::setShift(int x, int y)
{
std::vector<QRectF>areas;
if(shift.x() || shift.y())
{
for (auto * selectedObject : selectedObjects)
{
QRectF formerArea = getObjectArea(selectedObject);
formerArea.moveTo(formerArea.topLeft() + (shift * tileSize));
areas.emplace_back(formerArea);
}
}
shift = QPoint(x, y);
for (auto * selectedObject : selectedObjects)
{
QRectF area = getObjectArea(selectedObject);
area.moveTo(area.topLeft() + (shift * tileSize));
areas.emplace_back(area);
}
redraw(areas);
}
void SelectionObjectsLayer::setLockObject(CGObjectInstance * object, bool lock)
{
if(lock)
lockedObjects.insert(object);
else
lockedObjects.erase(object);
redraw({object});
}
void SelectionObjectsLayer::unlockAll()
{
lockedObjects.clear();
}
MinimapLayer::MinimapLayer(MapSceneBase * s): AbstractFixedLayer(s)
{
}
void MinimapLayer::update()
{
if(!map)
return;
pixmap = std::make_unique<QPixmap>(map->width, map->height);
QPainter painter(pixmap.get());
//coordinate transformation
for(int j = 0; j < map->height; ++j)
{
for(int i = 0; i < map->width; ++i)
{
handler->drawMinimapTile(painter, i, j, scene->level);
}
}
redraw();
}
MinimapViewLayer::MinimapViewLayer(MapSceneBase * s): AbstractFixedLayer(s)
{
}
void MinimapViewLayer::update()
{
if(!map)
return;
pixmap = std::make_unique<QPixmap>(map->width, map->height);
draw();
}
void MinimapViewLayer::draw()
{
if(!map)
return;
pixmap->fill(Qt::transparent);
//maybe not optimal but ok
QPainter painter(pixmap.get());
painter.setPen(Qt::white);
painter.drawRect(x, y, w, h);
redraw();
}
void MinimapViewLayer::setViewport(int _x, int _y, int _w, int _h)
{
x = _x;
y = _y;
w = _w;
h = _h;
draw();
}