void BucketFillTool::updateRandomList() { mRandomList.clear(); mMissingTilesets.clear(); foreach (const TileStampVariation &variation, mStamp.variations()) { TileLayer *tileLayer = static_cast<TileLayer*>(variation.map->layerAt(0)); mapDocument()->unifyTilesets(variation.map, mMissingTilesets); for (int x = 0; x < tileLayer->width(); x++) for (int y = 0; y < tileLayer->height(); y++) if (!tileLayer->cellAt(x, y).isEmpty()) mRandomList.append(tileLayer->cellAt(x, y)); } }
void MapToVariantConverter::addTileLayerData(QVariantMap &variant, const TileLayer &tileLayer, Map::LayerDataFormat format, const QRect &bounds) const { switch (format) { case Map::XML: case Map::CSV: { QVariantList tileVariants; for (int y = bounds.top(); y <= bounds.bottom(); ++y) for (int x = bounds.left(); x <= bounds.right(); ++x) tileVariants << mGidMapper.cellToGid(tileLayer.cellAt(x, y)); variant[QLatin1String("data")] = tileVariants; break; } case Map::Base64: case Map::Base64Zlib: case Map::Base64Gzip: { QByteArray layerData = mGidMapper.encodeLayerData(tileLayer, format, bounds); variant[QLatin1String("data")] = layerData; break; } } }
void TerrainBrush::capture() { TileLayer *tileLayer = currentTileLayer(); Q_ASSERT(tileLayer); // TODO: we need to know which corner the mouse is closest to... const Cell &cell = tileLayer->cellAt(tilePosition()); Terrain *t = cell.tile->terrainAtCorner(0); setTerrain(t); }
void SelectSameTileTool::tilePositionChanged(const QPoint &tilePos) { // Make sure that a tile layer is selected and contains current tile pos. TileLayer *tileLayer = currentTileLayer(); if (!tileLayer) return; QRegion resultRegion; if (tileLayer->contains(tilePos)) { const Cell &matchCell = tileLayer->cellAt(tilePos); resultRegion = tileLayer->region([&] (const Cell &cell) { return cell == matchCell; }); } mSelectedRegion = resultRegion; brushItem()->setTileRegion(mSelectedRegion); }
WangId WangFiller::wangIdFromSurroundings(const TileLayer &back, const QRegion &fillRegion, QPoint point) const { Cell surroundingCells[8]; QPoint adjacentPoints[8]; getSurroundingPoints(point, mStaggeredRenderer, mStaggerAxis, adjacentPoints); for (int i = 0; i < 8; ++i) { if (!fillRegion.contains(adjacentPoints[i])) surroundingCells[i] = back.cellAt(adjacentPoints[i]); } return mWangSet->wangIdFromSurrounding(surroundingCells); }
// Writer bool DroidcraftPlugin::write(const Tiled::Map *map, const QString &fileName) { using namespace Tiled; // Check layer count and type if (map->layerCount() != 1 || !map->layerAt(0)->isTileLayer()) { mError = tr("The map needs to have exactly one tile layer!"); return false; } TileLayer *mapLayer = map->layerAt(0)->asTileLayer(); // Check layer size if (mapLayer->width() != 48 || mapLayer->height() != 48) { mError = tr("The layer must have a size of 48 x 48 tiles!"); return false; } // Create QByteArray and compress it QByteArray uncompressed = QByteArray(48 * 48, 0); const int width = mapLayer->width(); const int height = mapLayer->height(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { if (Tile *tile = mapLayer->cellAt(x, y).tile) uncompressed[y * width + x] = (unsigned char) tile->id(); } } QByteArray compressed = compress(uncompressed, Gzip); // Write QByteArray QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { mError = tr("Could not open file for writing."); return false; } file.write(compressed); file.close(); return true; }
/** * Updates the list used random stamps. * This is done by taking all non-null tiles from the original stamp mStamp. */ void StampBrush::updateRandomList() { mRandomCellPicker.clear(); mMissingTilesets.clear(); if (mStamp.isEmpty()) return; foreach (const TileStampVariation &variation, mStamp.variations()) { TileLayer *tileLayer = static_cast<TileLayer*>(variation.map->layerAt(0)); mapDocument()->unifyTilesets(variation.map, mMissingTilesets); for (int x = 0; x < tileLayer->width(); x++) { for (int y = 0; y < tileLayer->height(); y++) { const Cell &cell = tileLayer->cellAt(x, y); if (!cell.isEmpty()) mRandomCellPicker.add(cell, cell.tile->probability()); } } } }
/** * Updates the list used random stamps. * This is done by taking all non-null tiles from the original stamp mStamp. */ void StampBrush::updateRandomList() { mRandomCellPicker.clear(); if (!mIsRandom) return; mMissingTilesets.clear(); for (const TileStampVariation &variation : mStamp.variations()) { mapDocument()->unifyTilesets(variation.map, mMissingTilesets); TileLayer *tileLayer = variation.tileLayer(); for (int x = 0; x < tileLayer->width(); x++) { for (int y = 0; y < tileLayer->height(); y++) { const Cell &cell = tileLayer->cellAt(x, y); if (const Tile *tile = cell.tile()) mRandomCellPicker.add(cell, tile->probability()); } } } }
void CellPropertiesTool::mousePressed(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { TileLayer *tileLayer = currentTileLayer(); Q_ASSERT(tileLayer); const QPoint tilePos = tilePosition(); Cell *cell = tileLayer->getCellAt(tilePos.x(),tilePos.y()); // Add properties from tile if (!cell->isEmpty() ) { Properties properties = cell->properties(); Properties tileprop = tileLayer->cellAt(tilePos.x(),tilePos.y()).tile->properties(); QStringList req = tileprop.value(QString::fromLatin1("required")).split(QString::fromLatin1(",")); req << tileprop.value(QString::fromLatin1("optional")).split(QString::fromLatin1(",")); foreach (const QString &str, req) { int p = str.indexOf(QString::fromLatin1("=")); QString key, value(QString::fromLatin1("")); if (0==p) { // only name key = str.trimmed(); } else { key = str.left(p).trimmed(); value = str.right(str.length() - p - 1); // options = value.split("/"); } if (key.isEmpty()) continue; // Keep the key if exist, otherwise insert the new value (empty by default) if (!properties.contains(key)) properties.insert(key, value); }
void BucketFillTool::tilePositionChanged(const QPoint &tilePos) { AbstractTileFillTool::tilePositionChanged(tilePos); if (isCapturing()) return; // Skip filling if the stamp is empty and not in wangFill mode if (mStamp.isEmpty() && mFillMethod != WangFill) 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); // 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 AbstractTileFillTool::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 computed from the current pos bool computeRegion = true; // If the stamp is a single tile, ignore that tile when making the region if (mFillMethod != WangFill && 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)) computeRegion = false; } if (computeRegion) 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->rect(); // 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 (mLastFillMethod != mFillMethod) { mLastFillMethod = mFillMethod; fillRegionChanged = true; } if (!mFillOverlay) { // Create a new overlay region const QRect fillBounds = mFillRegion.boundingRect(); mFillOverlay = SharedTileLayer::create(QString(), fillBounds.x(), fillBounds.y(), fillBounds.width(), fillBounds.height()); } // Paint the new overlay switch (mFillMethod) { case TileFill: if (fillRegionChanged || mStamp.variations().size() > 1) { fillWithStamp(*mFillOverlay, mStamp, mFillRegion.translated(-mFillOverlay->position())); fillRegionChanged = true; } break; case RandomFill: randomFill(*mFillOverlay, mFillRegion); fillRegionChanged = true; break; case WangFill: wangFill(*mFillOverlay, *tileLayer, mFillRegion); fillRegionChanged = true; break; } if (fillRegionChanged) { // Update the brush item to draw the overlay brushItem()->setTileLayer(mFillOverlay); } // Create connections to know when the overlay should be cleared makeConnections(); }
std::unique_ptr<TileLayer> WangFiller::fillRegion(const TileLayer &back, const QRegion &fillRegion) const { Q_ASSERT(mWangSet); const QRect boundingRect = fillRegion.boundingRect(); std::unique_ptr<TileLayer> tileLayer { new TileLayer(QString(), boundingRect.x(), boundingRect.y(), boundingRect.width(), boundingRect.height()) }; QVector<WangId> wangIds(tileLayer->width() * tileLayer->height(), 0); #if QT_VERSION < 0x050800 const auto rects = fillRegion.rects(); for (const QRect &rect : rects) { #else for (const QRect &rect : fillRegion) { #endif for (int x = rect.left(); x <= rect.right(); ++x) { int index = x - tileLayer->x() + (rect.top() - tileLayer->y()) * tileLayer->width(); wangIds[index] = wangIdFromSurroundings(back, fillRegion, QPoint(x, rect.top())); index = x - tileLayer->x() + (rect.bottom() - tileLayer->y()) * tileLayer->width(); wangIds[index] = wangIdFromSurroundings(back, fillRegion, QPoint(x, rect.bottom())); } for (int y = rect.top() + 1; y < rect.bottom(); ++y) { int index = rect.left() - tileLayer->x() + (y - tileLayer->y()) * tileLayer->width(); wangIds[index] = wangIdFromSurroundings(back, fillRegion, QPoint(rect.left(), y)); index = rect.right() - tileLayer->x() + (y - tileLayer->y()) * tileLayer->width(); wangIds[index] = wangIdFromSurroundings(back, fillRegion, QPoint(rect.right(), y)); } } #if QT_VERSION < 0x050800 for (const QRect &rect : rects) { #else for (const QRect &rect : fillRegion) { #endif for (int y = rect.top(); y <= rect.bottom(); ++y) { for (int x = rect.left(); x <= rect.right(); ++x) { QPoint currentPoint(x, y); int currentIndex = (currentPoint.y() - tileLayer->y()) * tileLayer->width() + (currentPoint.x() - tileLayer->x()); QList<WangTile> wangTilesList = mWangSet->findMatchingWangTiles(wangIds[currentIndex]); RandomPicker<WangTile> wangTiles; for (const WangTile &wangTile : wangTilesList) wangTiles.add(wangTile, mWangSet->wangTileProbability(wangTile)); while (!wangTiles.isEmpty()) { WangTile wangTile = wangTiles.take(); bool fill = true; if (!mWangSet->isComplete()) { QPoint adjacentPoints[8]; getSurroundingPoints(currentPoint, mStaggeredRenderer, mStaggerAxis, adjacentPoints); for (int i = 0; i < 8; ++i) { QPoint p = adjacentPoints[i]; if (!fillRegion.contains(p) || !tileLayer->cellAt(p - tileLayer->position()).isEmpty()) continue; p -= tileLayer->position(); int index = p.y() * tileLayer->width() + p.x(); WangId adjacentWangId = wangIds[index]; adjacentWangId.updateToAdjacent(wangTile.wangId(), (i + 4) % 8); if (!mWangSet->wildWangIdIsUsed(adjacentWangId)) { fill = wangTiles.isEmpty(); break; } } } if (fill) { tileLayer->setCell(currentPoint.x() - tileLayer->x(), currentPoint.y() - tileLayer->y(), wangTile.makeCell()); QPoint adjacentPoints[8]; getSurroundingPoints(currentPoint, mStaggeredRenderer, mStaggerAxis, adjacentPoints); for (int i = 0; i < 8; ++i) { QPoint p = adjacentPoints[i]; if (!fillRegion.contains(p) || !tileLayer->cellAt(p - tileLayer->position()).isEmpty()) continue; p -= tileLayer->position(); int index = p.y() * tileLayer->width() + p.x(); wangIds[index].updateToAdjacent(wangTile.wangId(), (i + 4) % 8); } break; } } } } } return tileLayer; } const Cell &WangFiller::getCell(const TileLayer &back, const TileLayer &front, const QRegion &fillRegion, QPoint point) const { if (!fillRegion.contains(point)) return back.cellAt(point); else return front.cellAt(point.x() - front.x(), point.y() - front.y()); } WangId WangFiller::wangIdFromSurroundings(const TileLayer &back, const TileLayer &front, const QRegion &fillRegion, QPoint point) const { Cell surroundingCells[8]; QPoint adjacentPoints[8]; getSurroundingPoints(point, mStaggeredRenderer, mStaggerAxis, adjacentPoints); for (int i = 0; i < 8; ++i) surroundingCells[i] = getCell(back, front, fillRegion, adjacentPoints[i]); return mWangSet->wangIdFromSurrounding(surroundingCells); } WangId WangFiller::wangIdFromSurroundings(const TileLayer &back, const QRegion &fillRegion, QPoint point) const { Cell surroundingCells[8]; QPoint adjacentPoints[8]; getSurroundingPoints(point, mStaggeredRenderer, mStaggerAxis, adjacentPoints); for (int i = 0; i < 8; ++i) { if (!fillRegion.contains(adjacentPoints[i])) surroundingCells[i] = back.cellAt(adjacentPoints[i]); } return mWangSet->wangIdFromSurrounding(surroundingCells); }
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(); }