Пример #1
0
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);
}
Пример #2
0
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 {
Пример #3
0
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);
}
Пример #4
0
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);
}
Пример #5
0
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();
}
Пример #6
0
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();
}
Пример #7
0
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;
}
Пример #8
0
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();
}