/* * 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(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() { QListemptyList; items.reset(scene->createItemGroup(emptyList)); } void AbstractViewportLayer::setViewport(const QRectF & viewPort) { if(!map) return; if (items->boundingRect().contains(viewPort)) return; std::vector outOfScreenSectors; for (QGraphicsItem * sector : getAllSectors()) { if (!viewPort.intersects(sector->sceneBoundingRect())) outOfScreenSectors.push_back(sector); } for (QGraphicsItem * sector : outOfScreenSectors) { removeSector(sector); } std::vector 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 allSectors; for (auto * sector : getAllSectors()) allSectors.insert(sector); redrawSectors(allSectors); } void AbstractViewportLayer::redraw(const std::vector & tiles) { std::set sectorsToRedraw = getContainingSectors(tiles); redrawSectors(sectorsToRedraw); } void AbstractViewportLayer::redrawWithSurroundingTiles(const std::vector & 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 & objects) { std::vector areas(objects.size()); for (const CGObjectInstance * object : objects) { areas.push_back(getObjectArea(object)); } redraw(areas); } void AbstractViewportLayer::redraw(const std::vector & areas) { std::set 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 & sectors) { std::set 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 AbstractViewportLayer::getAllSectors() const //returning const is necessary to avoid "range-loop might detach Qt container" problem { QList emptyList; return items ? items->childItems() : emptyList; } std::set AbstractViewportLayer::getContainingSectors(const std::vector & tiles) const { std::set 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 AbstractViewportLayer::getIntersectingSectors(const std::vector & areas) const { std::set 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(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 & 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 & tiles) { for (int3 tile : tiles) { if(!area.count(tile)) { area.insert(tile); } } redraw(tiles); onSelection(); } void SelectionTerrainLayer::erase(const std::vector & 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 & 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 & 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 & 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(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(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(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 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(o.obj)); } } } } selectObjects(selectedObjects); } void SelectionObjectsLayer::selectObject(CGObjectInstance * obj) { selectedObjects.insert(obj); onSelection(); redraw({obj}); } void SelectionObjectsLayer::selectObjects(const std::set & 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(obj)); } std::set 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::vectorareas; 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(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(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(); }