Merge pull request #670 from GriffinRichards/tileset-division

Keep metatile images on separate rows, fix metatile usage count
This commit is contained in:
GriffinR 2025-02-11 14:24:55 -05:00 committed by GitHub
commit 165d49f7da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 85 additions and 90 deletions

View File

@ -38,6 +38,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Reduced diff noise when saving maps.
- Map names and ``MAP_NAME`` constants are no longer required to match.
- Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``.
- Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8.
### Fixed
- Fix `Add Region Map...` not updating the region map settings file.
@ -79,6 +80,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix some problems with tileset detection when importing maps from AdvanceMap.
- Fix certain input fields allowing invalid identifiers, like names starting with numbers.
- Fix crash in the Shortcuts Editor when applying changes after closing certain windows.
- Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use.
## [5.4.1] - 2024-03-21
### Fixed

View File

@ -79,6 +79,7 @@ private:
bool positionIsValid(const QPoint &pos) const;
bool selectionIsValid();
void hoverChanged();
int numPrimaryMetatilesRounded() const;
signals:
void hoveredMetatileSelectionChanged(uint16_t);

View File

@ -51,6 +51,7 @@ private:
void drawCounts();
QImage buildAllMetatilesImage();
QImage buildImage(int metatileIdStart, int numMetatiles);
int numPrimaryMetatilesRounded() const;
signals:
void hoveredMetatileChanged(uint16_t);

View File

@ -1056,7 +1056,7 @@ bool Project::loadLayoutTilesets(Layout *layout) {
layout->tileset_primary = getTileset(layout->tileset_primary_label);
if (!layout->tileset_primary) {
QString defaultTileset = this->getDefaultPrimaryTilesetLabel();
logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_primary_label).arg(defaultTileset));
logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset));
layout->tileset_primary_label = defaultTileset;
layout->tileset_primary = getTileset(layout->tileset_primary_label);
if (!layout->tileset_primary) {
@ -1068,7 +1068,7 @@ bool Project::loadLayoutTilesets(Layout *layout) {
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
if (!layout->tileset_secondary) {
QString defaultTileset = this->getDefaultSecondaryTilesetLabel();
logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_secondary_label).arg(defaultTileset));
logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset));
layout->tileset_secondary_label = defaultTileset;
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
if (!layout->tileset_secondary) {
@ -1137,7 +1137,8 @@ bool Project::loadBlockdata(Layout *layout) {
layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight());
if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) {
logWarn(QString("Layout blockdata length %1 does not match dimensions %2x%3 (should be %4). Resizing blockdata.")
logWarn(QString("%1 blockdata length %2 does not match dimensions %3x%4 (should be %5). Resizing blockdata.")
.arg(layout->name)
.arg(layout->blockdata.count())
.arg(layout->getWidth())
.arg(layout->getHeight())
@ -1174,7 +1175,8 @@ bool Project::loadLayoutBorder(Layout *layout) {
int borderLength = layout->getBorderWidth() * layout->getBorderHeight();
if (layout->border.count() != borderLength) {
logWarn(QString("Layout border blockdata length %1 must be %2. Resizing border blockdata.")
logWarn(QString("%1 border blockdata length %2 must be %3. Resizing border blockdata.")
.arg(layout->name)
.arg(layout->border.count())
.arg(borderLength));
layout->border.resize(borderLength);

View File

@ -9,12 +9,17 @@ QPoint MetatileSelector::getSelectionDimensions() {
return SelectablePixmapItem::getSelectionDimensions();
}
int MetatileSelector::numPrimaryMetatilesRounded() const {
// We round up the number of primary metatiles to keep the tilesets on separate rows.
return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide;
}
void MetatileSelector::draw() {
if (!this->primaryTileset || !this->secondaryTileset) {
this->setPixmap(QPixmap());
}
int primaryLength = this->primaryTileset->numMetatiles();
int primaryLength = this->numPrimaryMetatilesRounded();
int length_ = primaryLength + this->secondaryTileset->numMetatiles();
int height_ = length_ / this->numMetatilesWide;
if (length_ % this->numMetatilesWide != 0) {
@ -149,7 +154,7 @@ void MetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
void MetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
QPoint pos = this->getCellPos(event->pos());
if (!positionIsValid(pos) || this->cellPos == pos)
if (this->cellPos == pos)
return;
this->cellPos = pos;
@ -199,10 +204,11 @@ void MetatileSelector::updateExternalSelectedMetatiles() {
uint16_t MetatileSelector::getMetatileId(int x, int y) const {
int index = y * this->numMetatilesWide + x;
if (index < this->primaryTileset->numMetatiles()) {
int numPrimary = this->numPrimaryMetatilesRounded();
if (index < numPrimary) {
return static_cast<uint16_t>(index);
} else {
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles());
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - numPrimary);
}
}
@ -215,7 +221,7 @@ QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) {
int index = metatileId < Project::getNumMetatilesPrimary()
? metatileId
: metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles();
: metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded();
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
}

View File

@ -29,13 +29,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
this->tileYFlip = ui->checkBox_yFlip->isChecked();
this->paletteId = ui->spinBox_paletteSelector->value();
// TODO: The dividing line at the moment is only accurate if the number of primary metatiles is divisible by 8.
// If it's not, the secondary metatiles will wrap above the line. This has other problems (like skewing
// metatile groups the user may have designed) so this should be fixed by filling the primary metatiles
// image with invalid magenta metatiles until it's divisible by 8. Then the line can be re-enabled as-is.
ui->actionShow_Tileset_Divider->setChecked(/*porymapConfig.showTilesetEditorDivider*/false);
ui->actionShow_Tileset_Divider->setVisible(false);
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->spinBox_paletteSelector->setMinimum(0);
ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this));
@ -759,7 +753,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered()
{
QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
dialog.setWindowTitle("Change Number of Metatiles");
dialog.setWindowModality(Qt::NonModal);
dialog.setWindowModality(Qt::WindowModal);
QFormLayout form(&dialog);
@ -1051,20 +1045,19 @@ void TilesetEditor::countMetatileUsage() {
// do not double count
metatileSelector->usedMetatiles.fill(0);
for (auto layout : this->project->mapLayouts.values()) {
bool usesPrimary = false;
bool usesSecondary = false;
for (auto layout : this->project->mapLayouts) {
// It's possible for a layout's tileset labels to change if they are invalid,
// so we need to load all the tilesets even if they aren't the tileset we're looking for.
// Otherwise the metatile usage counts may change because the layouts with invalid tilesets
// were updated to use a tileset we were looking for.
this->project->loadLayoutTilesets(layout);
if (layout->tileset_primary_label == this->primaryTileset->name) {
usesPrimary = true;
}
if (layout->tileset_secondary_label == this->secondaryTileset->name) {
usesSecondary = true;
}
bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name);
bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name);
if (usesPrimary || usesSecondary) {
this->project->loadLayout(layout);
if (!this->project->loadLayout(layout))
continue;
// for each block in the layout, mark in the vector that it is used
for (int i = 0; i < layout->blockdata.length(); i++) {
@ -1097,9 +1090,9 @@ void TilesetEditor::countTileUsage() {
QSet<Tileset*> secondaryTilesets;
for (auto layout : this->project->mapLayouts.values()) {
this->project->loadLayoutTilesets(layout);
if (layout->tileset_primary_label == this->primaryTileset->name
|| layout->tileset_secondary_label == this->secondaryTileset->name) {
this->project->loadLayoutTilesets(layout);
// need to check metatiles
if (layout->tileset_primary && layout->tileset_secondary) {
primaryTilesets.insert(layout->tileset_primary);

View File

@ -22,11 +22,16 @@ int TilesetEditorMetatileSelector::numRows(int numMetatiles) {
}
int TilesetEditorMetatileSelector::numRows() {
return this->numRows(this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles());
return this->numRows(this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles());
}
int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const {
// We round up the number of primary metatiles to keep the tilesets on separate rows.
return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide;
}
QImage TilesetEditorMetatileSelector::buildAllMetatilesImage() {
return this->buildImage(0, this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles());
return this->buildImage(0, this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles());
}
QImage TilesetEditorMetatileSelector::buildPrimaryMetatilesImage() {
@ -39,11 +44,11 @@ QImage TilesetEditorMetatileSelector::buildSecondaryMetatilesImage() {
QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMetatiles) {
int numMetatilesHigh = this->numRows(numMetatiles);
int numPrimary = this->primaryTileset->numMetatiles();
int numPrimary = this->numPrimaryMetatilesRounded();
int maxPrimary = Project::getNumMetatilesPrimary();
bool includesPrimary = metatileIdStart < maxPrimary;
QImage image(this->numMetatilesWide * 32, numMetatilesHigh * 32, QImage::Format_RGBA8888);
QImage image(this->numMetatilesWide * this->cellWidth, numMetatilesHigh * this->cellHeight, QImage::Format_RGBA8888);
image.fill(Qt::magenta);
QPainter painter(&image);
for (int i = 0; i < numMetatiles; i++) {
@ -57,10 +62,10 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet
this->layout->metatileLayerOrder,
this->layout->metatileLayerOpacity,
true)
.scaled(32, 32);
.scaled(this->cellWidth, this->cellHeight);
int map_y = i / this->numMetatilesWide;
int map_x = i % this->numMetatilesWide;
QPoint metatile_origin = QPoint(map_x * 32, map_y * 32);
QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight);
painter.drawImage(metatile_origin, metatile_image);
}
painter.end();
@ -107,10 +112,11 @@ uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() {
uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) {
int index = y * this->numMetatilesWide + x;
if (index < this->primaryTileset->numMetatiles()) {
int numPrimary = numPrimaryMetatilesRounded();
if (index < numPrimary) {
return static_cast<uint16_t>(index);
} else {
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles());
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - numPrimary);
}
}
@ -156,7 +162,7 @@ QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) {
}
int index = metatileId < Project::getNumMetatilesPrimary()
? metatileId
: metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles();
: metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded();
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
}
@ -176,12 +182,12 @@ void TilesetEditorMetatileSelector::drawGrid() {
const int numColumns = this->numMetatilesWide;
const int numRows = this->numRows();
for (int column = 1; column < numColumns; column++) {
int x = column * 32;
painter.drawLine(x, 0, x, numRows * 32);
int x = column * this->cellWidth;
painter.drawLine(x, 0, x, numRows * this->cellHeight);
}
for (int row = 1; row < numRows; row++) {
int y = row * 32;
painter.drawLine(0, y, numColumns * 32, y);
int y = row * this->cellHeight;
painter.drawLine(0, y, numColumns * this->cellWidth, y);
}
painter.end();
this->setPixmap(pixmap);
@ -191,12 +197,12 @@ void TilesetEditorMetatileSelector::drawDivider() {
if (!this->showDivider)
return;
const int y = this->numRows(this->primaryTileset->numMetatiles()) * 32;
const int y = this->numRows(this->numPrimaryMetatilesRounded()) * this->cellHeight;
QPixmap pixmap = this->pixmap();
QPainter painter(&pixmap);
painter.setPen(Qt::white);
painter.drawLine(0, y, this->numMetatilesWide * 32, y);
painter.drawLine(0, y, this->numMetatilesWide * this->cellWidth, y);
painter.end();
this->setPixmap(pixmap);
}
@ -212,7 +218,7 @@ void TilesetEditorMetatileSelector::drawFilters() {
void TilesetEditorMetatileSelector::drawUnused() {
// setup the circle with a line through it image to layer above unused metatiles
QPixmap redX(32, 32);
QPixmap redX(this->cellWidth, this->cellHeight);
redX.fill(Qt::transparent);
QPen whitePen(Qt::white);
@ -223,21 +229,21 @@ void TilesetEditorMetatileSelector::drawUnused() {
QPainter oPainter(&redX);
oPainter.setPen(whitePen);
oPainter.drawEllipse(QRect(1, 1, 30, 30));
oPainter.drawEllipse(QRect(1, 1, this->cellWidth - 2, this->cellHeight - 2));
oPainter.setPen(pinkPen);
oPainter.drawEllipse(QRect(2, 2, 28, 28));
oPainter.drawEllipse(QRect(3, 3, 26, 26));
oPainter.drawEllipse(QRect(2, 2, this->cellWidth - 4, this->cellHeight - 4));
oPainter.drawEllipse(QRect(3, 3, this->cellWidth - 6, this->cellHeight - 6));
oPainter.setPen(whitePen);
oPainter.drawEllipse(QRect(4, 4, 24, 24));
oPainter.drawEllipse(QRect(4, 4, this->cellHeight - 8, this->cellHeight - 8));
whitePen.setWidth(5);
oPainter.setPen(whitePen);
oPainter.drawLine(0, 0, 31, 31);
oPainter.drawLine(0, 0, this->cellWidth - 1, this->cellHeight - 1);
pinkPen.setWidth(3);
oPainter.setPen(pinkPen);
oPainter.drawLine(2, 2, 29, 29);
oPainter.drawLine(2, 2, this->cellWidth - 3, this->cellHeight - 3);
oPainter.end();
@ -247,19 +253,13 @@ void TilesetEditorMetatileSelector::drawUnused() {
QPainter unusedPainter(&metatilesPixmap);
unusedPainter.setOpacity(0.5);
int primaryLength = this->primaryTileset->numMetatiles();
int length_ = primaryLength + this->secondaryTileset->numMetatiles();
for (int i = 0; i < length_; i++) {
int tile = i;
if (i >= primaryLength) {
tile += Project::getNumMetatilesPrimary() - primaryLength;
}
if (!usedMetatiles[tile]) {
unusedPainter.drawPixmap((i % 8) * 32, (i / 8) * 32, redX);
}
for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) {
if (this->usedMetatiles.at(metatileId) || !Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
continue;
// Adjust position from center to top-left corner
QPoint pos = getMetatileIdCoordsOnWidget(metatileId) - QPoint(this->cellWidth / 2, this->cellHeight / 2);
unusedPainter.drawPixmap(pos.x(), pos.y(), redX);
}
unusedPainter.end();
this->setPixmap(metatilesPixmap);
@ -268,38 +268,28 @@ void TilesetEditorMetatileSelector::drawUnused() {
void TilesetEditorMetatileSelector::drawCounts() {
QPen blackPen(Qt::black);
blackPen.setWidth(1);
QPixmap metatilesPixmap = this->pixmap();
QPainter countPainter(&metatilesPixmap);
countPainter.setPen(blackPen);
for (int tile = 0; tile < this->usedMetatiles.size(); tile++) {
int count = usedMetatiles[tile];
QString countText = QString::number(count);
if (count > 1000) countText = ">1k";
countPainter.drawText((tile % 8) * 32, (tile / 8) * 32 + 32, countText);
}
// write in white and black for contrast
QPen whitePen(Qt::white);
whitePen.setWidth(1);
countPainter.setPen(whitePen);
int primaryLength = this->primaryTileset->numMetatiles();
int length_ = primaryLength + this->secondaryTileset->numMetatiles();
QPixmap metatilesPixmap = this->pixmap();
QPainter countPainter(&metatilesPixmap);
for (int i = 0; i < length_; i++) {
int tile = i;
if (i >= primaryLength) {
tile += Project::getNumMetatilesPrimary() - primaryLength;
}
int count = usedMetatiles[tile];
QString countText = QString::number(count);
if (count > 1000) countText = ">1k";
countPainter.drawText((i % 8) * 32 + 1, (i / 8) * 32 + 32 - 1, countText);
for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) {
if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
continue;
int count = this->usedMetatiles.at(metatileId);
QString countText = (count > 1000) ? QStringLiteral(">1k") : QString::number(count);
// Adjust position from center to bottom-left corner
QPoint pos = getMetatileIdCoordsOnWidget(metatileId) + QPoint(-(this->cellWidth / 2), this->cellHeight / 2);
// write in black and white for contrast
countPainter.setPen(blackPen);
countPainter.drawText(pos.x(), pos.y(), countText);
countPainter.setPen(whitePen);
countPainter.drawText(pos.x() + 1, pos.y() - 1, countText);
}
countPainter.end();
this->setPixmap(metatilesPixmap);