void StampBrush::endCapture() { if (mBrushBehavior != Capture) return; mBrushBehavior = Free; TileLayer *tileLayer = currentTileLayer(); Q_ASSERT(tileLayer); // Intersect with the layer and translate to layer coordinates QRect captured = capturedArea(); captured &= QRect(tileLayer->x(), tileLayer->y(), tileLayer->width(), tileLayer->height()); if (captured.isValid()) { captured.translate(-tileLayer->x(), -tileLayer->y()); Map *map = tileLayer->map(); TileLayer *capture = tileLayer->copy(captured); Map *stamp = new Map(map->orientation(), capture->width(), capture->height(), map->tileWidth(), map->tileHeight()); // Add tileset references to map foreach (const SharedTileset &tileset, capture->usedTilesets()) stamp->addTileset(tileset); stamp->addLayer(capture); emit stampCaptured(TileStamp(stamp)); } else {
void StampBrush::endCapture() { if (mBrushBehavior != Capture) return; mBrushBehavior = Free; TileLayer *tileLayer = currentTileLayer(); Q_ASSERT(tileLayer); // Intersect with the layer and translate to layer coordinates QRect captured = capturedArea(); captured.intersect(QRect(tileLayer->x(), tileLayer->y(), tileLayer->width(), tileLayer->height())); if (captured.isValid()) { captured.translate(-tileLayer->x(), -tileLayer->y()); TileLayer *capture = tileLayer->copy(captured); emit currentTilesChanged(capture); // A copy will have been created, so delete this version delete capture; } else { updatePosition(); } }
/** * Returns a new stamp where all variations have been flipped in the given * \a direction. */ TileStamp TileStamp::flipped(FlipDirection direction) const { TileStamp flipped(*this); flipped.d.detach(); for (const TileStampVariation &variation : flipped.variations()) { TileLayer *layer = variation.tileLayer(); if (variation.map->isStaggered()) { Map::StaggerAxis staggerAxis = variation.map->staggerAxis(); if (staggerAxis == Map::StaggerY) { if ((direction == FlipVertically && !(layer->height() & 1)) || direction == FlipHorizontally) variation.map->invertStaggerIndex(); } else { if ((direction == FlipHorizontally && !(layer->width() & 1)) || direction == FlipVertically) variation.map->invertStaggerIndex(); } } if (variation.map->orientation() == Map::Hexagonal) layer->flipHexagonal(direction); else layer->flip(direction); } return flipped; }
// 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; }
static void fillWithStamp(TileLayer &layer, const TileStamp &stamp, const QRegion &mask) { const QSize size = stamp.maxSize(); // Fill the entire layer with random variations of the stamp for (int y = 0; y < layer.height(); y += size.height()) { for (int x = 0; x < layer.width(); x += size.width()) { const TileStampVariation variation = stamp.randomVariation(); layer.setCells(x, y, variation.tileLayer()); } } // Erase tiles outside of the masked region. This can easily be faster than // avoiding to place tiles outside of the region in the first place. layer.erase(QRegion(0, 0, layer.width(), layer.height()) - mask); }
void StampBrush::endCapture() { if (mBrushBehavior != Capture) return; mBrushBehavior = Free; TileLayer *tileLayer = currentTileLayer(); Q_ASSERT(tileLayer); // Intersect with the layer and translate to layer coordinates QRect captured = capturedArea(); captured &= QRect(tileLayer->x(), tileLayer->y(), tileLayer->width(), tileLayer->height()); if (captured.isValid()) { captured.translate(-tileLayer->x(), -tileLayer->y()); Map *map = tileLayer->map(); TileLayer *capture = tileLayer->copy(captured); Map *stamp = new Map(map->orientation(), capture->width(), capture->height(), map->tileWidth(), map->tileHeight()); //gets if the relative stagger should be the same as the base layer int staggerIndexOffSet; if (tileLayer->map()->staggerAxis() == Map::StaggerX) staggerIndexOffSet = captured.x() % 2; else staggerIndexOffSet = captured.y() % 2; stamp->setStaggerAxis(map->staggerAxis()); stamp->setStaggerIndex((Map::StaggerIndex)((map->staggerIndex() + staggerIndexOffSet) % 2)); // Add tileset references to map foreach (const SharedTileset &tileset, capture->usedTilesets()) stamp->addTileset(tileset); stamp->addLayer(capture); emit stampCaptured(TileStamp(stamp)); } else {
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)); } }
/** * Returns a new stamp where all variations have been rotated in the given * \a direction. */ TileStamp TileStamp::rotated(RotateDirection direction) const { TileStamp rotated(*this); rotated.d.detach(); foreach (const TileStampVariation &variation, rotated.variations()) { TileLayer *layer = static_cast<TileLayer*>(variation.map->layerAt(0)); layer->rotate(direction); variation.map->setWidth(layer->width()); variation.map->setHeight(layer->height()); } return rotated; }
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 {
/** * Returns a new stamp where all variations have been rotated in the given * \a direction. */ TileStamp TileStamp::rotated(RotateDirection direction) const { TileStamp rotated(*this); rotated.d.detach(); for (const TileStampVariation &variation : rotated.variations()) { TileLayer *layer = variation.tileLayer(); if (variation.map->orientation() == Map::Hexagonal) layer->rotateHexagonal(direction, variation.map); else layer->rotate(direction); variation.map->setWidth(layer->width()); variation.map->setHeight(layer->height()); } return rotated; }
/** * 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 StampBrush::doPaint(bool mergeable, int whereX, int whereY) { TileLayer *stamp = brushItem()->tileLayer(); if (!stamp) 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, stamp->width(), stamp->height()))) return; PaintTileLayer *paint = new PaintTileLayer(mapDocument(), tileLayer, whereX, whereY, stamp); paint->setMergeable(mergeable); mapDocument()->undoStack()->push(paint); mapDocument()->emitRegionEdited(brushItem()->tileRegion(), tileLayer); }
void test_MapReader::loadMap() { MapReader reader; Map *map = reader.readMap("../data/mapobject.tmx"); // TODO: Also test tilesets (internal and external), properties and tile // layer data. QVERIFY(map); QCOMPARE(map->layerCount(), 2); QCOMPARE(map->width(), 100); QCOMPARE(map->height(), 80); QCOMPARE(map->tileWidth(), 32); QCOMPARE(map->tileHeight(), 32); TileLayer *tileLayer = dynamic_cast<TileLayer*>(map->layerAt(0)); QVERIFY(tileLayer); QCOMPARE(tileLayer->width(), 100); QCOMPARE(tileLayer->height(), 80); ObjectGroup *objectGroup = dynamic_cast<ObjectGroup*>(map->layerAt(1)); QVERIFY(objectGroup); QCOMPARE(objectGroup->name(), QLatin1String("Objects")); QCOMPARE(objectGroup->objects().count(), 1); MapObject *mapObject = objectGroup->objects().at(0); QCOMPARE(mapObject->name(), QLatin1String("Some object")); QCOMPARE(mapObject->type(), QLatin1String("WARP")); QCOMPARE(mapObject->x(), qreal(200)); QCOMPARE(mapObject->y(), qreal(200)); QCOMPARE(mapObject->width(), qreal(128)); QCOMPARE(mapObject->height(), qreal(64)); }
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) { 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(); }
TileLayer *WangFiller::fillRegion(const TileLayer &back, const QRegion &fillRegion) const { Q_ASSERT(mWangSet); QRect boundingRect = fillRegion.boundingRect(); TileLayer *tileLayer = new TileLayer(QString(), boundingRect.x(), boundingRect.y(), boundingRect.width(), boundingRect.height()); QVector<WangId> wangIds(tileLayer->width() * tileLayer->height(), 0); for (const QRect &rect : fillRegion.rects()) { 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)); } } for (const QRect &rect : fillRegion.rects()) { 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; }
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; }