void Eraser::doErase(bool mergeable) { TileLayer *tileLayer = currentTileLayer(); const QPoint tilePos = tilePosition(); if (!tileLayer->bounds().contains(tilePos)) return; QRegion eraseRegion(tilePos.x(), tilePos.y(), 1, 1); EraseTiles *erase = new EraseTiles(mapDocument(), tileLayer, eraseRegion); erase->setMergeable(mergeable); mapDocument()->undoStack()->push(erase); mapDocument()->emitRegionEdited(eraseRegion, tileLayer); }
void TerrainBrush::updateBrush(QPoint cursorPos, const QVector<QPoint> *list) { // get the current tile layer TileLayer *currentLayer = currentTileLayer(); Q_ASSERT(currentLayer); int layerWidth = currentLayer->width(); int layerHeight = currentLayer->height(); int numTiles = layerWidth * layerHeight; int paintCorner = 0; // if we are in vertex paint mode, the bottom right corner on the map will appear as an invalid tile offset... if (mBrushMode == PaintVertex) { if (cursorPos.x() == layerWidth) { cursorPos.setX(cursorPos.x() - 1); paintCorner |= 1; } if (cursorPos.y() == layerHeight) { cursorPos.setY(cursorPos.y() - 1); paintCorner |= 2; } } // if the cursor is outside of the map, bail out if (!currentLayer->bounds().contains(cursorPos)) return; // TODO: this seems like a problem... there's nothing to say that 2 adjacent tiles are from the same tileset, or have any relation to eachother... Tileset *terrainTileset = mTerrain ? mTerrain->tileset() : NULL; // allocate a buffer to build the terrain tilemap (TODO: this could be retained per layer to save regular allocation) Tile **newTerrain = new Tile*[numTiles]; // allocate a buffer of flags for each tile that may be considered (TODO: this could be retained per layer to save regular allocation) char *checked = new char[numTiles]; memset(checked, 0, numTiles); // create a consideration list, and push the start points QList<QPoint> transitionList; int initialTiles = 0; if (list) { // if we were supplied a list of start points foreach (const QPoint &p, *list) { transitionList.push_back(p); ++initialTiles; } } else {
void StampBrush::doPaint(bool mergeable, int whereX, int whereY) { if (!mStamp) return; // This method shouldn't be called when current layer is not a tile layer TileLayer *tileLayer = currentTileLayer(); Q_ASSERT(tileLayer); if (!tileLayer->bounds().intersects(QRect(whereX, whereY, mStamp->width(), mStamp->height()))) return; PaintTileLayer *paint = new PaintTileLayer(mapDocument(), tileLayer, whereX, whereY, brushItem()->tileLayer()); paint->setMergeable(mergeable); mapDocument()->undoStack()->push(paint); mapDocument()->emitRegionEdited(brushItem()->tileRegion(), tileLayer); }
void Eraser::doErase(bool continuation) { TileLayer *tileLayer = currentTileLayer(); const QPoint tilePos = tilePosition(); QRegion eraseRegion(tilePos.x(), tilePos.y(), 1, 1); if (continuation) { for (const QPoint &p : pointsOnLine(mLastTilePos, tilePos)) eraseRegion |= QRegion(p.x(), p.y(), 1, 1); } mLastTilePos = tilePosition(); if (!tileLayer->bounds().intersects(eraseRegion.boundingRect())) return; EraseTiles *erase = new EraseTiles(mapDocument(), tileLayer, eraseRegion); erase->setMergeable(continuation); mapDocument()->undoStack()->push(erase); mapDocument()->emitRegionEdited(eraseRegion, tileLayer); }
void BucketFillTool::tilePositionChanged(const QPoint &tilePos) { bool shiftPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier; // Optimization: we don't need to recalculate the fill area // if the new mouse position is still over the filled region // and the shift modifier hasn't changed. if (mFillRegion.contains(tilePos) && shiftPressed == mLastShiftStatus) return; // Cache information about how the fill region was created mLastShiftStatus = shiftPressed; // Clear overlay to make way for a new one // This also clears the connections so we don't get callbacks clearOverlay(); // Skip filling if the stamp is empty if (!mStamp || mStamp->isEmpty()) return; // Make sure that a tile layer is selected TileLayer *tileLayer = currentTileLayer(); if (!tileLayer) return; // Get the new fill region if (!shiftPressed) { // If not holding shift, a region is generated from the current pos TilePainter regionComputer(mapDocument(), tileLayer); // If the stamp is a single tile, ignore it when making the region if (mStamp->width() == 1 && mStamp->height() == 1 && mStamp->cellAt(0, 0) == regionComputer.cellAt(tilePos.x(), tilePos.y())) return; mFillRegion = regionComputer.computeFillRegion(tilePos); } else { // If holding shift, the region is the selection bounds mFillRegion = mapDocument()->tileSelection(); // Fill region is the whole map is there is no selection if (mFillRegion.isEmpty()) mFillRegion = tileLayer->bounds(); // The mouse needs to be in the region if (!mFillRegion.contains(tilePos)) mFillRegion = QRegion(); } // Ensure that a fill region was created before making an overlay layer if (mFillRegion.isEmpty()) return; // Create a new overlay region mFillOverlay = new TileLayer(QString(), tileLayer->x(), tileLayer->y(), tileLayer->width(), tileLayer->height()); // Paint the new overlay TilePainter tilePainter(mapDocument(), mFillOverlay); tilePainter.drawStamp(mStamp, mFillRegion); // Crop the overlay to the smallest possible size const QRect fillBounds = mFillRegion.boundingRect(); mFillOverlay->resize(fillBounds.size(), -fillBounds.topLeft()); mFillOverlay->setX(fillBounds.x()); mFillOverlay->setY(fillBounds.y()); // Update the brush item to draw the overlay brushItem()->setTileLayer(mFillOverlay); // Create connections to know when the overlay should be cleared makeConnections(); }
void BucketFillTool::tilePositionChanged(const QPoint &tilePos) { // Skip filling if the stamp is empty if (mStamp.isEmpty()) return; // Make sure that a tile layer is selected TileLayer *tileLayer = currentTileLayer(); if (!tileLayer) return; bool shiftPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier; bool fillRegionChanged = false; TilePainter regionComputer(mapDocument(), tileLayer); // If the stamp is a single tile, ignore it when making the region if (!shiftPressed && mStamp.variations().size() == 1) { const TileStampVariation &variation = mStamp.variations().first(); TileLayer *stampLayer = variation.tileLayer(); if (stampLayer->size() == QSize(1, 1) && stampLayer->cellAt(0, 0) == regionComputer.cellAt(tilePos)) return; } // This clears the connections so we don't get callbacks clearConnections(mapDocument()); // Optimization: we don't need to recalculate the fill area // if the new mouse position is still over the filled region // and the shift modifier hasn't changed. if (!mFillRegion.contains(tilePos) || shiftPressed != mLastShiftStatus) { // Clear overlay to make way for a new one clearOverlay(); // Cache information about how the fill region was created mLastShiftStatus = shiftPressed; // Get the new fill region if (!shiftPressed) { // If not holding shift, a region is generated from the current pos mFillRegion = regionComputer.computePaintableFillRegion(tilePos); } else { // If holding shift, the region is the selection bounds mFillRegion = mapDocument()->selectedArea(); // Fill region is the whole map if there is no selection if (mFillRegion.isEmpty()) mFillRegion = tileLayer->bounds(); // The mouse needs to be in the region if (!mFillRegion.contains(tilePos)) mFillRegion = QRegion(); } fillRegionChanged = true; } // Ensure that a fill region was created before making an overlay layer if (mFillRegion.isEmpty()) return; if (mLastRandomStatus != mIsRandom) { mLastRandomStatus = mIsRandom; fillRegionChanged = true; } if (!mFillOverlay) { // Create a new overlay region const QRect fillBounds = mFillRegion.boundingRect(); mFillOverlay = SharedTileLayer(new TileLayer(QString(), fillBounds.x(), fillBounds.y(), fillBounds.width(), fillBounds.height())); } // Paint the new overlay if (!mIsRandom) { if (fillRegionChanged || mStamp.variations().size() > 1) { fillWithStamp(*mFillOverlay, mStamp, mFillRegion.translated(-mFillOverlay->position())); fillRegionChanged = true; } } else { randomFill(*mFillOverlay, mFillRegion); fillRegionChanged = true; } if (fillRegionChanged) { // Update the brush item to draw the overlay brushItem()->setTileLayer(mFillOverlay); } // Create connections to know when the overlay should be cleared makeConnections(); }
QVariant MapToVariantConverter::toVariant(const TileLayer &tileLayer, Map::LayerDataFormat format) const { QVariantMap tileLayerVariant; tileLayerVariant[QLatin1String("type")] = QLatin1String("tilelayer"); QRect bounds = tileLayer.bounds().translated(-tileLayer.position()); if (tileLayer.map()->infinite()) { tileLayerVariant[QLatin1String("width")] = bounds.width(); tileLayerVariant[QLatin1String("height")] = bounds.height(); tileLayerVariant[QLatin1String("startx")] = bounds.left(); tileLayerVariant[QLatin1String("starty")] = bounds.top(); } else { tileLayerVariant[QLatin1String("width")] = tileLayer.width(); tileLayerVariant[QLatin1String("height")] = tileLayer.height(); } addLayerAttributes(tileLayerVariant, tileLayer); switch (format) { case Map::XML: case Map::CSV: break; case Map::Base64: case Map::Base64Zlib: case Map::Base64Gzip: tileLayerVariant[QLatin1String("encoding")] = QLatin1String("base64"); if (format == Map::Base64Zlib) tileLayerVariant[QLatin1String("compression")] = QLatin1String("zlib"); else if (format == Map::Base64Gzip) tileLayerVariant[QLatin1String("compression")] = QLatin1String("gzip"); break; } if (tileLayer.map()->infinite()) { QVariantList chunkVariants; for (const QRect &rect : tileLayer.sortedChunksToWrite()) { QVariantMap chunkVariant; chunkVariant[QLatin1String("x")] = rect.x(); chunkVariant[QLatin1String("y")] = rect.y(); chunkVariant[QLatin1String("width")] = rect.width(); chunkVariant[QLatin1String("height")] = rect.height(); addTileLayerData(chunkVariant, tileLayer, format, rect); chunkVariants.append(chunkVariant); } tileLayerVariant[QLatin1String("chunks")] = chunkVariants; } else { addTileLayerData(tileLayerVariant, tileLayer, format, QRect(0, 0, tileLayer.width(), tileLayer.height())); } return tileLayerVariant; }
void BucketFillTool::tilePositionChanged(const QPoint &tilePos) { if (mStamp.isEmpty()) return; bool shiftPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier; bool fillRegionChanged = false; // Make sure that a tile layer is selected TileLayer *tileLayer = currentTileLayer(); if (!tileLayer) return; // todo: When there are multiple variations, it would make sense to choose // random variations while filling. When the variations have different // sizes, probably the bounding box of all variations should be used. Map *variation = mStamp.randomVariation(); TileLayer *stampLayer = static_cast<TileLayer*>(variation->layerAt(0)); // Skip filling if the stamp is empty if (!stampLayer || stampLayer->isEmpty()) return; TilePainter regionComputer(mapDocument(), tileLayer); // If the stamp is a single tile, ignore it when making the region if (stampLayer->width() == 1 && stampLayer->height() == 1 && !shiftPressed && stampLayer->cellAt(0, 0) == regionComputer.cellAt(tilePos.x(), tilePos.y())) return; // This clears the connections so we don't get callbacks clearConnections(mapDocument()); // Optimization: we don't need to recalculate the fill area // if the new mouse position is still over the filled region // and the shift modifier hasn't changed. if (!mFillRegion.contains(tilePos) || shiftPressed != mLastShiftStatus || mIsRandom) { // Clear overlay to make way for a new one clearOverlay(); // Cache information about how the fill region was created mLastShiftStatus = shiftPressed; // Get the new fill region if (!shiftPressed) { // If not holding shift, a region is generated from the current pos mFillRegion = regionComputer.computePaintableFillRegion(tilePos); } else { // If holding shift, the region is the selection bounds mFillRegion = mapDocument()->selectedArea(); // Fill region is the whole map if there is no selection if (mFillRegion.isEmpty()) mFillRegion = tileLayer->bounds(); // The mouse needs to be in the region if (!mFillRegion.contains(tilePos)) mFillRegion = QRegion(); } fillRegionChanged = true; } // Ensure that a fill region was created before making an overlay layer if (mFillRegion.isEmpty()) return; if (mLastRandomStatus != mIsRandom) { mLastRandomStatus = mIsRandom; fillRegionChanged = true; } if (!mFillOverlay) { // Create a new overlay region const QRect fillBounds = mFillRegion.boundingRect(); mFillOverlay = SharedTileLayer(new TileLayer(QString(), fillBounds.x(), fillBounds.y(), fillBounds.width(), fillBounds.height())); } // Paint the new overlay if (!mIsRandom) { if (fillRegionChanged) { mMissingTilesets.clear(); mapDocument()->unifyTilesets(variation, mMissingTilesets); TilePainter tilePainter(mapDocument(), mFillOverlay.data()); tilePainter.drawStamp(stampLayer, mFillRegion); } } else { randomFill(mFillOverlay.data(), mFillRegion); fillRegionChanged = true; } if (fillRegionChanged) { // Update the brush item to draw the overlay brushItem()->setTileLayer(mFillOverlay); } // Create connections to know when the overlay should be cleared makeConnections(); }