SharedTileset readTileset(const QString &fileName, QString *error) { // Try the first registered tileset format that claims to support the file if (TilesetFormat *format = findSupportingFormat(fileName)) { SharedTileset tileset = format->read(fileName); if (error) { if (!tileset) *error = format->errorString(); else *error = QString(); } if (tileset) tileset->setFormat(format); return tileset; } // Fall back to default reader (TSX format) MapReader reader; SharedTileset tileset = reader.readTileset(fileName); if (error) { if (!tileset) *error = reader.errorString(); else *error = QString(); } return tileset; }
int main(int argc, char *argv[]) { QGuiApplication a(argc, argv); a.setOrganizationDomain(QLatin1String("mapeditor.org")); a.setApplicationName(QLatin1String("TerrainGenerator")); a.setApplicationVersion(QLatin1String("1.0")); CommandLineOptions options; if (!parseCommandLineArguments(options)) { // Something went wrong, abort. return 1; } if (options.showVersion) showVersion(); if (options.showHelp) showHelp(); if (options.showVersion || options.showHelp) return 0; if (options.target.isEmpty()) { qWarning() << "Error: No target tileset provided"; showHelp(); return 1; } if (options.sources.isEmpty()) { qWarning() << "Error: No source tilesets provided"; showHelp(); return 1; } // Check terrain priorities. if (options.terrainPriority.isEmpty()) { qWarning("Error: No terrain priorities set (option -p)."); showHelp(); return 1; } MapReader reader; SharedTileset targetTileset; QList<SharedTileset> sources; if (!options.overwrite && QFile::exists(options.target)) { targetTileset = reader.readTileset(options.target); if (!targetTileset) { qCritical("Error reading target tileset:\n%s", qUtf8Printable(reader.errorString())); } // Remove empty tiles from the end of the tileset int nextTileId = targetTileset->nextTileId(); for (int id = nextTileId - 1; id >= 0; --id) { if (Tile *tile = targetTileset->findTile(id)) { if (isEmpty(tile->image().toImage())) { targetTileset->deleteTile(id); nextTileId = id; continue; } } break; } targetTileset->setNextTileId(nextTileId); // If the target tileset already exists, it is also a source tileset sources.append(targetTileset); } // Read source tilesets. for (const QString &sourceFileName : options.sources) { SharedTileset source = reader.readTileset(sourceFileName); if (!source) { qCritical("Error reading source tileset '%s':\n%s", qUtf8Printable(sourceFileName), qUtf8Printable(reader.errorString())); } sources.append(source); } // If the target tileset does not exist yet, create it if (!targetTileset) { QString name = QFileInfo(options.target).completeBaseName(); const SharedTileset &firstSource = sources.first(); int tileWidth = firstSource->tileWidth(); int tileHeight = firstSource->tileHeight(); targetTileset = Tileset::create(name, tileWidth, tileHeight); } // Set up a mapping from terrain to tile, for quick lookup QMap<TileTerrainNames, Tile*> terrainToTile; for (const SharedTileset &tileset : sources) for (Tile *tile : tileset->tiles()) if (tile->terrain() != 0xFFFFFFFF) if (!terrainToTile.contains(TileTerrainNames(tile))) // TODO: Optimize terrainToTile.insert(TileTerrainNames(tile), tile); // Set up the list of all terrains, mapped by name. QMap<QString, Terrain*> terrains; for (const SharedTileset &tileset : sources) for (Terrain *terrain : tileset->terrains()) if (!terrains.contains(terrain->name())) terrains.insert(terrain->name(), terrain); // Check if there is anything to combine. if (options.combineList.size() == 0) { qWarning() << "No terrain specified to combine (-c option)."; } else { // Dump the combine lists. qWarning() << "Terrains to combine:"; for (const QStringList &combine : options.combineList) { if (combine.isEmpty()) { qCritical("Empty combine set"); } qWarning() << combine; // Make sure every terrain from this set was defined. for (const QString &terrainName : combine) if (!terrains.contains(terrainName)) qCritical("Terrain %s is in combine list, however it wasn't defined by any tileset.", qUtf8Printable(terrainName)); } } // Setup terrain priorities. TerrainLessThan lessThan; int priority = 0; for (const QString &terrainName : options.terrainPriority) { lessThan.terrainPriority.insert(terrainName, priority); ++priority; } qDebug() << "Terrains found:" << terrains.keys(); // Check if all terrains from priority list were found and loaded. for (const QString &terrainName : lessThan.terrainPriority.keys()) if (!terrains.contains(terrainName)) qWarning() << "Terrain" << terrainName << "from priority list not found."; // Add terrain names not specified from command line. for (const QString &terrainName : terrains.keys()) { if (!lessThan.terrainPriority.contains(terrainName)) { qWarning() << "No priority set for" << terrainName; lessThan.terrainPriority.insert(terrainName, priority); ++priority; } } // Add terrains that are not defined in the target tileset yet // TODO: This step should be more configurable for (Terrain *terrain : terrains) { if (!hasTerrain(*targetTileset, terrain->name())) { Tile *terrainTile = terrain->imageTile(); QPixmap terrainImage = terrainTile->image(); Tile *newTerrainTile = targetTileset->addTile(terrainImage); newTerrainTile->setProperties(terrainTile->properties()); Terrain *newTerrain = targetTileset->addTerrain(terrain->name(), newTerrainTile->id()); // WARNING: This assumes the terrain tile has this terrain on all // its corners. newTerrainTile->setTerrain(makeTerrain(newTerrain->id())); terrainToTile.insert(TileTerrainNames(newTerrainTile), newTerrainTile); } } // Prepare a list of terrain combinations. QVector<TileTerrainNames> process; for (const QStringList &combine : options.combineList) { QList<Terrain*> terrainList; // Get the terrains to combine for (const QString &terrainName : combine) terrainList.append(terrains[terrainName]); // Construct a vector with all terrain combinations to process for (Terrain *topLeft : terrainList) { for (Terrain *topRight : terrainList) { for (Terrain *bottomLeft : terrainList) { for (Terrain *bottomRight : terrainList) { process.append(TileTerrainNames(topLeft->name(), topRight->name(), bottomLeft->name(), bottomRight->name())); } } } } } // Go through each combination of terrains and add the tile to the target // tileset if it's not in there yet. for (const TileTerrainNames &terrainNames : process) { Tile *tile = terrainToTile.value(terrainNames); if (tile && tile->tileset() == targetTileset) continue; QPixmap image; Properties properties; if (!tile) { qWarning() << "Generating" << terrainNames; // Start a new image QImage tileImage = QImage(targetTileset->tileWidth(), targetTileset->tileHeight(), QImage::Format_ARGB32); tileImage.fill(Qt::transparent); QPainter painter(&tileImage); QStringList terrainList = terrainNames.terrainList(); qSort(terrainList.begin(), terrainList.end(), lessThan); // Draw the lowest terrain to avoid pixel gaps QString baseTerrain = terrainList.first(); QPixmap baseImage = terrains[baseTerrain]->imageTile()->image(); painter.drawPixmap(0, 0, baseImage); for (const QString &terrainName : terrainList) { TileTerrainNames filtered = terrainNames.filter(terrainName); Tile *tile = terrainToTile.value(filtered); if (!tile) { qWarning() << "Missing" << filtered; continue; } painter.drawPixmap(0, 0, tile->image()); properties.merge(tile->properties()); } image = QPixmap::fromImage(tileImage); } else { qWarning() << "Copying" << terrainNames << "from" << QFileInfo(tile->tileset()->fileName()).fileName(); image = tile->image(); properties = tile->properties(); } Tile *newTile = targetTileset->addTile(image); newTile->setTerrain(terrainNames.toTerrain(*targetTileset)); newTile->setProperties(properties); terrainToTile.insert(terrainNames, newTile); } if (targetTileset->tileCount() == 0) qCritical("Target tileset is empty"); if (options.embedImage) { // Make sure there is no source name, this way the image will be saved in the TSX file. targetTileset->setImageSource(QString()); } else { // Save the target tileset image as separate file. int columns = qMin(options.columns, targetTileset->tileCount()); int rows = targetTileset->tileCount() / options.columns; if (targetTileset->tileCount() % options.columns > 0) ++rows; qWarning() << "Writing external tileset image."; // Save the target tileset image QImage image(targetTileset->tileWidth() * columns, targetTileset->tileHeight() * rows, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); for (Tile *tile : targetTileset->tiles()) { int x = (tile->id() % options.columns) * targetTileset->tileWidth(); int y = (tile->id() / options.columns) * targetTileset->tileHeight(); painter.drawPixmap(x, y, tile->image()); } QString imageFileName = QFileInfo(options.target).completeBaseName(); imageFileName += ".png"; image.save(imageFileName); targetTileset->setImageSource(imageFileName); targetTileset->setColumnCount(options.columns); } // Save the target tileset MapWriter writer; targetTileset->setFileName(QString()); writer.writeTileset(*targetTileset, options.target); return 0; }