Skip to content

Instantly share code, notes, and snippets.

@Kruithne
Last active December 6, 2019 15:05
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/8d472e944f233fa5bb1b861fcd275582 to your computer and use it in GitHub Desktop.
Save Kruithne/8d472e944f233fa5bb1b861fcd275582 to your computer and use it in GitHub Desktop.
Clunky MDX converter
// WARNING - This was written in a rush, late at night. Mostly broken, not good code.
// UV mappings are slightly wrong, scale Y -1 to fix.
// Requires modded Bufo found here -> https://gist.github.com/Kruithne/679eb3b3e5099360cbe4c5872f2aa606
// Only exports Verts, Faces, UV and Normals to OBJ format (no MTL, skin it yourself!)
const fs = require('fs');
const Bufo = require('./bufo.js');
const path = require('path');
const util = require('util');
const argv = process.argv.splice(2);
for (let file of argv) {
if (!fs.existsSync(file)) {
console.error('Unable to locate file: %s', file);
continue;
}
let filePath = path.resolve(file);
let bin = new Bufo(fs.readFileSync(file));
let magic = bin.readString(4);
if (magic !== 'MDLX') {
console.error('Invalid magic? %s', magic);
continue;
}
let out = [];
//let faceIndex = 1;
let groupsOut = [];
let vertsOut = []
let normalsOut = [];
let uvOut = [];
while (bin.remainingBytes > 0) {
let chunkHeader = bin.readString(4);
let chunkLength = bin.readUInt32();
if (chunkHeader === 'VERS') {
let version = bin.readUInt32();
console.log('%s (version %d mdx)', filePath, version);
} else if (chunkHeader === 'MODL') {
let modelName = bin.readString(50).replace(/\0/g, '');
out.push('# Model ' + modelName);
console.log('Model name: %s', modelName);
bin.move(chunkLength - 50);
} else if (chunkHeader === 'GEOS') {
let chunk = bin.readBufo(chunkLength);
let ofs = [];
while (chunk.remainingBytes > 0) {
let byte = chunk.readUInt8();
let byteOfs = chunk.offset - 1;
if (byte === 0x56) {
chunk.move(-1);
if (chunk.readString(4) === 'VRTX') {
ofs.push(byteOfs);
} else {
chunk.move(-3);
}
}
}
for (let i = 0; i < ofs.length; i++) {
let offset = ofs[i];
chunk.seek(offset + 4);
groupsOut.push('o ' + i);
let vertCount = chunk.readUInt32();
let vertsStartIndex = vertsOut.length;
for (let i = 0; i < vertCount; i++) {
let verts = chunk.readFloat(3);
vertsOut.push('v ' + verts.join(' '));
}
chunk.move(4); // NRMs
let nrmCount = chunk.readUInt32();
for (let i = 0; i < nrmCount; i++) {
let nrms = chunk.readFloat(3);
normalsOut.push('vn ' + nrms.join(' '));
}
chunk.move(4); // PTYP
chunk.move(chunk.readUInt32() * 4);
chunk.move(4); // PCNT
chunk.move(chunk.readUInt32() * 4);
chunk.move(4); // PVTX
let faceCountRaw = chunk.readUInt32();
let faceCount = faceCountRaw / 3;
for (let i = 0; i < faceCount; i++) {
let face = chunk.readUInt16(3);
let f1 = vertsStartIndex + face[0] + 1;
let f2 = vertsStartIndex + face[1] + 1;
let f3 = vertsStartIndex + face[2] + 1;
groupsOut.push(util.format('f %d/%d/%d %d/%d/%d %d/%d/%d', f1, f1, f1, f2, f2, f2, f3, f3, f3));
}
while (chunk.remainingBytes > 0) {
let byte = chunk.readUInt8();
if (byte === 0x55) {
chunk.move(-1);
if (chunk.readString(4) === 'UVBS') {
let uvCount = chunk.readUInt32();
for (let i = 0; i < uvCount; i++) {
let uv = chunk.readFloat(2);
uvOut.push('vt ' + uv.join(' '));
}
} else {
chunk.move(-3);
}
}
}
}
} else {
console.log('Unknown chunk: %s (%d bytes)', chunkHeader, chunkLength);
bin.move(chunkLength);
}
}
if (out.length > 0) {
for (let node of vertsOut)
out.push(node);
for (let node of normalsOut)
out.push(node);
for (let node of uvOut)
out.push(node);
for (let node of groupsOut)
out.push(node);
let outFile = path.join(path.dirname(filePath), path.basename(filePath, '.mdx') + '.obj');
fs.writeFileSync(outFile, out.join('\n'), 'utf8');
console.log('Writing %s', outFile);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment