Skip to content

Instantly share code, notes, and snippets.

@Kruithne
Created December 2, 2020 01:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kruithne/b203936ec49d4cfb70f7af439e5a378f to your computer and use it in GitHub Desktop.
Save Kruithne/b203936ec49d4cfb70f7af439e5a378f to your computer and use it in GitHub Desktop.
Tile Merge script for wow.export
// Tile Merge Script by Kruithne
// Disclaimer: This was written for a very specific purpose and comes with no warranty.
// If this explodes, ping @Kruithne in Party Tortollans Discord (wow.export channel).
// If this teleports your dog into the vacuum of space, please do not contact me.
// Before Usage:
// - Make sure you have Node v15.3.0+ installed (https://nodejs.org/en/)
// Usage:
// 1) Copy the contents of this script to a file called 'tile_merge.js'
// 2) Open a command prompt (or PowerShell, etc) in the same directory as the script.
// 3) If this is your first time, run "node ./tile_merge.js" (without the quotation marks) followed by "npm install" as instructed.
// 4) Run "node ./tile_merge.js" with directories containing tiles to merge as arguments, example: "node ./tile_merge D:\wow.export.out\maps\azeroth"
// Notes:
// - Tiles must be in "tex_<x>_<y>_<index>.png" format.
// - 256 files are not required, but tile count must be even to form a square.
// - All tiles must match the size of the first tile as the final size is not pre-calculated by assumed based on the first.
const fs = require('fs');
const path = require('path');
const PACKAGE_NAME = 'package.json';
(async () => {
// User has never run the script before, write a package file for dependencies.
if (!fs.existsSync(PACKAGE_NAME)) {
fs.writeFileSync(PACKAGE_NAME, JSON.stringify({
"dependencies": {
"jimp": "^0.16.1"
}
}, null, '\t'));
console.log('Boop! First run detected, a package manifest has been written.');
console.log('Run "npm install" in this directory now to install dependencies.');
return;
}
const Jimp = require('jimp');
const argv = process.argv.splice(2);
// User has provided no arguments to the script.
if (argv.length === 0)
return console.log('No arguments provided. Example syntax: node .\\tile_merge.js D:\\wow.export.out\\maps\\azeroth');
// Iterate over all arguments provided.
for (let mapDir of argv) {
mapDir = path.resolve(mapDir);
// Ensure provided path exists.
if (!fs.existsSync(mapDir))
return console.log('Failed: Unable to resolve provided directory: %s', mapDir);
// Ensure provided path is a directory.
const dirStat = fs.statSync(mapDir);
if (!dirStat.isDirectory())
return console.log('Failed: Provided path exists but is not a directory: %s', mapDir);
// Retrieve a directory listing and filter it to only include files.
const entries = fs.readdirSync(mapDir, { withFileTypes: true }).filter(e => e.isFile());
const tileMap = new Map();
// Iterate files and separate files by ADT ID.
for (const entry of entries) {
const match = entry.name.match(/tex_(\d+_\d+)_(\d+).png/);
if (match !== null) {
const adtID = match[1];
const entryPath = path.join(mapDir, entry.name);
const tileIndex = parseInt(match[2]);
const insertEntry = { entryPath, tileIndex };
const tileMapEntry = tileMap.get(adtID);
tileMapEntry ? tileMapEntry.push(insertEntry) : tileMap.set(adtID, [insertEntry]);
}
}
// To ensure tiles write to the correct place, let's sort them quickly.
for (const fileList of tileMap.values())
fileList.sort((a, b) => a.tileIndex < b.tileIndex ? -1 : (a.tileIndex > b.tileIndex ? 1 : 0));
for (const [adtID, files] of tileMap) {
// To combine tiles in a square, we need an even number of tiles.
if (files.length % 2 !== 0)
return console.log('Failed: Map tile %s does not have an even number of tiles.', adtID);
const tileCount = Math.sqrt(files.length);
console.log('Stitching %s (%d files, %d x %d)', adtID, files.length, tileCount, tileCount);
let tileIndex = 0;
let tileSize = null;
let tileCanvas = null;
for (let y = 0; y < tileCount; y++) {
for (let x = 0; x < tileCount; x++) {
const tileEntry = files[tileIndex++];
const tileFile = tileEntry.entryPath;
const tileData = await Jimp.read(tileFile);
const tileWidth = tileData.bitmap.width;
const tileHeight = tileData.bitmap.height;
// If tileSize has not yet been set, set to the dimensions of the
// first tile. From this we decide the resulting canvas size.
if (tileSize === null) {
// First tile is not uniform size, that's a problem.
if (tileWidth !== tileHeight)
return console.log('Failed: Map tile is not a uniform size (%d != %d) @ %s', tileWidth, tileHeight, tileFile);
tileSize = tileWidth;
// Create a new canvas to fit all of the tiles on.
const canvasSize = tileSize * tileCount;
tileCanvas = await new Promise((resolve, reject) => {
new Jimp(canvasSize, canvasSize, (err, image) => {
err ? reject(err) : resolve(image);
});
});
} else {
// Every tile must match the dimensions of the first since we don't check them all in advance.
if (tileWidth !== tileSize || tileHeight !== tileSize)
return console.log('Failed: Map tile does not match the expected size (%d, %d != %d, %d) @ %s', tileWidth, tileHeight, tileSize, tileSize, tileFile);
}
// Composite the tile onto the canvas at the correct location.
tileCanvas.composite(tileData, x * tileSize, y * tileSize);
}
}
// All done? Save the canvas.
const outputFile = path.join(mapDir, adtID + '_joined.png');
tileCanvas.write(outputFile);
console.log('Merged tiles written to %s', outputFile);
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment