-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: add binmap plugin #4235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: add binmap plugin #4235
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
Binary Map Format Specification | ||
=============================== | ||
|
||
Simplest format possible. Consist of a 16 bytes long header and then data. | ||
All numbers are little-endian, and except of the magic all are uint16_t's. | ||
It does not store map properties, tileset specification, objects, etc.; | ||
deliberately just the pure tile identifiers and nothing else. | ||
|
||
Header | ||
------ | ||
|
||
| Offset | Size | Description | | ||
|-------:|-----:|---------------------------| | ||
| 0 | 3 | magic bytes `MAP` | | ||
| 3 | 1 | format version (always 0) | | ||
| 4 | 2 | map width in tiles | | ||
| 6 | 2 | map height in tiles | | ||
| 8 | 2 | number of layers | | ||
| 10 | 2 | tile width in pixels | | ||
| 12 | 2 | tile height in pixels | | ||
| 14 | 2 | orientation | | ||
|
||
Orientation goes like: 0 = orthographic, 1 = isometric, 2 = hexagonal horizontal, 3 = hexagonal vertical. | ||
|
||
Layer Data | ||
---------- | ||
|
||
The header is followed by *number of layers* layer blocks, each *map height x map width x 2* bytes long. | ||
|
||
Within each layer, first comes the top row, then the second row, etc. Within each row first the left most | ||
column comes, then the second column, etc. Each cell value is a tile id stored as an unsigned short value | ||
on 2 bytes (just the global tile id, without flags). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
Tiled BinMap Export Plugin | ||
========================== | ||
|
||
This is a [Tiled](https://mapeditor.org) plugin to dump tilemaps into a brainfuckingly simple binary format. | ||
|
||
It ships BinMap as a plugin (which can be compiled into an .so file) as well as an extension (a .js file). | ||
|
||
Installation | ||
------------ | ||
|
||
You'll only need one of these, as they do exactly the same. | ||
|
||
### JavaScript Extension | ||
|
||
Copy `src/plugins/binmap/binmap-format.js` into `~/.config/tiled/extensions`. | ||
|
||
### Tiled Plugin | ||
|
||
1. Download the tiled source from [github](https://github.com/mapeditor/tiled). | ||
2. Install all its dependencies (see its README for details). | ||
3. Copy `src/plugins/binmap` directory into the downloaded repository. | ||
4. Edit `src/plugins/plugins.qbs` and add `"binmap",` to the list. | ||
5. In the repository's main directory, run `qbs`. | ||
|
||
Documentation | ||
------------- | ||
|
||
The file format specification can be found [here](src/plugins/binmap/FORMAT.md). It is the simplest format possible, | ||
just contains a fixed sized header and the uncompressed tile ids for each layer, that's all. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/// <reference types="@mapeditor/tiled-api" /> | ||
|
||
/* | ||
* binmap-format.js | ||
* | ||
* This extension adds the 'binary map format' type to the Export As menu. | ||
* This is an alternative to the .so plugin, written in JavaScript. | ||
* To install, copy to `~/.config/tiled/extensions` | ||
* | ||
* Simplest format possible. Consist of a 16 bytes long header and then data. | ||
* See FORMAT.md for details. | ||
*/ | ||
|
||
tiled.registerMapFormat("binmap", { | ||
name: "Binary map files", | ||
extension: "map", | ||
|
||
write: (map, fileName) => { | ||
let numLayers = 0, gid = 1; | ||
/* fix missing firstgid properties on tilesets */ | ||
for (let i = 0; i < map.tilesets.length; ++i) { | ||
map.tilesets[i].firstgid = gid; | ||
gid += map.tilesets[i].tileCount; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll have to note that using If you want to support image collection tilesets, you'd currently need to iterate all tiles to find the maximum used tile ID. |
||
} | ||
/* count tile type layers */ | ||
for (let i = 0; i < map.layerCount; ++i) { | ||
const layer = map.layerAt(i); | ||
if (layer.isTileLayer) numLayers++; | ||
} | ||
/* file and buffers */ | ||
var file = new BinaryFile(fileName, BinaryFile.WriteOnly); | ||
let header = new ArrayBuffer(16); | ||
let item = new ArrayBuffer(2); | ||
let hu16 = new Uint16Array(header); | ||
let iu16 = new Uint16Array(item); | ||
/* write header */ | ||
hu16[0] = 0x414D; | ||
hu16[1] = 0x0050; | ||
hu16[2] = map.width; | ||
hu16[3] = map.height; | ||
hu16[4] = numLayers; | ||
hu16[5] = map.tileWidth; | ||
hu16[6] = map.tileHeight; | ||
/* looks like the Javascript API can't handle hexagonal maps */ | ||
hu16[7] = map.Isometric ? 1 : 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
file.write(header); | ||
/* write layer tiles */ | ||
for (let i = 0; i < map.layerCount; ++i) { | ||
const layer = map.layerAt(i); | ||
if (!layer.isTileLayer) continue; | ||
|
||
for (let y = 0; y < map.height; ++y) { | ||
for (let x = 0; x < map.width; ++x) { | ||
const tile = layer.tileAt(x, y); | ||
/* convert local tile id to global tile id */ | ||
iu16[0] = tile == null ? 0 : tile.id + (tile.tileset ? tile.tileset.firstgid : 1); | ||
file.write(item); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a performance perspective I'd suggest writing entire layers at once rather than tile-by-tile. |
||
} | ||
} | ||
} | ||
file.commit(); | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* BinMap File Format | ||
* Copyright 2025, bzt <bztsrc@gitlab> | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License as published by the Free | ||
* Software Foundation; either version 2 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
* Simplest format possible. Consist of a 16 bytes long header and then data. | ||
* See FORMAT.md for details. | ||
*/ | ||
|
||
extern "C" { | ||
|
||
#include <stdint.h> | ||
|
||
typedef struct { | ||
char magic[3]; /* 00 magic "MAP" */ | ||
uint8_t version; /* 03 format version */ | ||
uint16_t mapWidth; /* 04 map width */ | ||
uint16_t mapHeight; /* 06 map height */ | ||
uint16_t numLayers; /* 08 number of layers */ | ||
uint16_t tileWidth; /* 0A tile width */ | ||
uint16_t tileHeight; /* 0C tile height */ | ||
uint16_t orientation; /* 0E orientation (see O_* enums) */ | ||
} __attribute__((packed)) binmap_header_t; | ||
|
||
enum { O_ORTHO, O_ISO, O_HEXH, O_HEXV }; | ||
|
||
/* Header followed by mapWidth * mapHeight * numLayers uint16_t and that's it. */ | ||
|
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
TiledPlugin { | ||
cpp.defines: base.concat(["BINMAP_LIBRARY"]) | ||
|
||
files: [ | ||
"binmap_global.h", | ||
"binmapplugin.cpp", | ||
"binmapplugin.h", | ||
"plugin.json", | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* BinMap Tiled Plugin | ||
* Copyright 2025, bzt <bztsrc@gitlab> | ||
* | ||
* This file is part of Tiled. | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License as published by the Free | ||
* Software Foundation; either version 2 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <QtCore/qglobal.h> | ||
|
||
#if defined(BINMAP_LIBRARY) | ||
# define BINMAPSHARED_EXPORT Q_DECL_EXPORT | ||
#else | ||
# define BINMAPSHARED_EXPORT Q_DECL_IMPORT | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
* BinMap Tiled Plugin | ||
* Copyright 2025, bzt <bztsrc@gitlab> | ||
* | ||
* This file is part of Tiled. | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License as published by the Free | ||
* Software Foundation; either version 2 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
* To install, copy this directory under `tiled/src/plugins/binmap` directory, | ||
* and add `"binmap",` line to `tiled/src/plugins/plugins.qbs` file. | ||
* | ||
* Simplest format possible. Consist of a 16 bytes long header and then data. | ||
* See FORMAT.md for details. | ||
*/ | ||
|
||
#include "binmapplugin.h" | ||
|
||
#include "gidmapper.h" | ||
#include "map.h" | ||
#include "mapobject.h" | ||
#include "savefile.h" | ||
#include "tile.h" | ||
#include "tiled.h" | ||
#include "tilelayer.h" | ||
#include "tileset.h" | ||
#include "objectgroup.h" | ||
|
||
#include <QCoreApplication> | ||
#include <QDir> | ||
#include <QFileInfo> | ||
#include <QStringList> | ||
#include <QStringView> | ||
#include <QTextStream> | ||
|
||
#include <memory> | ||
#include <fstream> | ||
|
||
#include "binmap.h" | ||
|
||
using namespace Tiled; | ||
|
||
namespace Binmap { | ||
|
||
BinmapPlugin::BinmapPlugin() | ||
{ | ||
} | ||
|
||
QString BinmapPlugin::nameFilter() const | ||
{ | ||
return tr("Binary map files (*.map)"); | ||
} | ||
|
||
QString BinmapPlugin::shortName() const | ||
{ | ||
return QStringLiteral("binmap"); | ||
} | ||
|
||
QString BinmapPlugin::errorString() const | ||
{ | ||
return mError; | ||
} | ||
|
||
bool BinmapPlugin::write(const Tiled::Map *map, const QString &fileName, Options options) | ||
{ | ||
Q_UNUSED(options) | ||
binmap_header_t header; | ||
|
||
// construct header | ||
memset( &header, 0, sizeof(header) ); | ||
header.magic[0] = 'M'; | ||
header.magic[1] = 'A'; | ||
header.magic[2] = 'P'; | ||
header.version = 0; | ||
header.mapWidth = map->width(); | ||
header.mapHeight = map->height(); | ||
header.tileWidth = map->tileWidth(); | ||
header.tileHeight = map->tileHeight(); | ||
switch( map->orientation() ) { | ||
case Map::Orthogonal: header.orientation = O_ORTHO; break; | ||
case Map::Isometric: header.orientation = O_ISO; break; | ||
case Map::Hexagonal: header.orientation = map->staggerAxis() == Map::StaggerX ? O_HEXH : O_HEXV; break; | ||
default: | ||
mError = QCoreApplication::translate("File Errors", "Unsupported map format."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not support |
||
return false; | ||
break; | ||
} | ||
// count layers | ||
for (Layer *layer : map->layers()) { | ||
if (layer->asTileLayer()) header.numLayers++; | ||
} | ||
|
||
// open file | ||
std::ofstream file(fileName.toStdString(), std::ios::trunc | std::ios::binary); | ||
if (!file) { | ||
mError = QCoreApplication::translate("File Errors", "Could not open file for writing."); | ||
return false; | ||
} | ||
|
||
GidMapper gidMapper(map->tilesets()); | ||
|
||
// write header | ||
file.write( reinterpret_cast< const char* >( &header ), sizeof(header) ); | ||
|
||
// write layers | ||
for (Layer *layer : map->layers()) { | ||
if (TileLayer *tileLayer = layer->asTileLayer()) { | ||
for (int y = 0; y < map->height(); ++y) { | ||
for (int x = 0; x < map->width(); ++x) { | ||
Cell t = tileLayer->cellAt(x, y); | ||
unsigned short id = gidMapper.cellToGid(t); | ||
file.write( reinterpret_cast< const char* >( &id ), sizeof(id) ); | ||
} | ||
} | ||
} | ||
} | ||
|
||
file.close(); | ||
|
||
return true; | ||
} | ||
|
||
} // namespace Flare |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* BinMap Tiled Plugin | ||
* Copyright 2025, bzt <bztsrc@gitlab> | ||
* | ||
* This file is part of Tiled. | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License as published by the Free | ||
* Software Foundation; either version 2 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "binmap_global.h" | ||
|
||
#include "mapformat.h" | ||
|
||
#include <QMap> | ||
#include <QObject> | ||
#include <QTextStream> | ||
|
||
namespace Binmap { | ||
|
||
class BINMAPSHARED_EXPORT BinmapPlugin : public Tiled::WritableMapFormat | ||
{ | ||
Q_OBJECT | ||
Q_INTERFACES(Tiled::MapFormat) | ||
Q_PLUGIN_METADATA(IID "org.mapeditor.MapFormat" FILE "plugin.json") | ||
|
||
public: | ||
BinmapPlugin(); | ||
|
||
bool write(const Tiled::Map *map, const QString &fileName, Options options) override; | ||
QString nameFilter() const override; | ||
QString shortName() const override; | ||
QString errorString() const override; | ||
|
||
private: | ||
QString mError; | ||
}; | ||
|
||
} // namespace Binmap |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{ "defaultEnable": true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a "fix". "First global IDs" simply don't exist outside of the file format. It's fine to assign them as dynamic properties like this, but you'll want to reword that comment. :-)