Merge pull request #61 from LucasDower/schem-exporter

Added .schem exporter, closes #60
This commit is contained in:
Lucas Dower 2022-07-09 01:10:07 +01:00 committed by GitHub
commit 3377872cd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 190 additions and 33 deletions

65
package-lock.json generated
View File

@ -15,7 +15,8 @@
"jpeg-js": "^0.4.4", "jpeg-js": "^0.4.4",
"pngjs": "^6.0.0", "pngjs": "^6.0.0",
"prismarine-nbt": "^1.6.0", "prismarine-nbt": "^1.6.0",
"twgl.js": "^4.19.1" "twgl.js": "^4.19.1",
"varint-array": "^0.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
@ -23,6 +24,7 @@
"@types/obj-file-parser": "^0.5.0", "@types/obj-file-parser": "^0.5.0",
"@types/pngjs": "^6.0.1", "@types/pngjs": "^6.0.1",
"@types/prompt": "^1.1.2", "@types/prompt": "^1.1.2",
"@types/varint": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^5.9.1", "@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1", "@typescript-eslint/parser": "^5.9.1",
"adm-zip": "^0.5.9", "adm-zip": "^0.5.9",
@ -1435,6 +1437,15 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true "dev": true
}, },
"node_modules/@types/varint": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@types/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-2jBazyxGl4644tvu3VAez8UA/AtrcEetT9HOeAbqZ/vAcRVL/ZDFQjSS7rkWusU5cyONQVUz+nwwrNZdMva4ow==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "16.0.4", "version": "16.0.4",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
@ -6638,6 +6649,14 @@
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/struct": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/struct/-/struct-0.0.11.tgz",
"integrity": "sha512-zhCBDK3POhX5y1IUTr5JXY7l9Esjdi1WkhZBjZEJU8ewpwsa95l+PtLCMrYIu8VxDFCb7vr2CnuQDxDEj2K63g==",
"engines": {
"node": ">0.6.0"
}
},
"node_modules/sumchecker": { "node_modules/sumchecker": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
@ -7105,6 +7124,20 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"node_modules/varint-array": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/varint-array/-/varint-array-0.0.0.tgz",
"integrity": "sha512-mUtmdj8Ic9LO6Vu+AgRtPP8tPBmnliJ7xbRRabwvW+qRVkOYlp/7o3Ga27Wq7xr8KcR6W1GDe+gmm/5hCtqEMQ==",
"dependencies": {
"struct": "0.0.11",
"varint": "^5.0.0"
}
},
"node_modules/varint-array/node_modules/varint": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz",
"integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow=="
},
"node_modules/w3c-hr-time": { "node_modules/w3c-hr-time": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
@ -8489,6 +8522,15 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true "dev": true
}, },
"@types/varint": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@types/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-2jBazyxGl4644tvu3VAez8UA/AtrcEetT9HOeAbqZ/vAcRVL/ZDFQjSS7rkWusU5cyONQVUz+nwwrNZdMva4ow==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/yargs": { "@types/yargs": {
"version": "16.0.4", "version": "16.0.4",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
@ -12486,6 +12528,11 @@
} }
} }
}, },
"struct": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/struct/-/struct-0.0.11.tgz",
"integrity": "sha512-zhCBDK3POhX5y1IUTr5JXY7l9Esjdi1WkhZBjZEJU8ewpwsa95l+PtLCMrYIu8VxDFCb7vr2CnuQDxDEj2K63g=="
},
"sumchecker": { "sumchecker": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
@ -12818,6 +12865,22 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"varint-array": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/varint-array/-/varint-array-0.0.0.tgz",
"integrity": "sha512-mUtmdj8Ic9LO6Vu+AgRtPP8tPBmnliJ7xbRRabwvW+qRVkOYlp/7o3Ga27Wq7xr8KcR6W1GDe+gmm/5hCtqEMQ==",
"requires": {
"struct": "0.0.11",
"varint": "^5.0.0"
},
"dependencies": {
"varint": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz",
"integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow=="
}
}
},
"w3c-hr-time": { "w3c-hr-time": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",

View File

@ -36,6 +36,7 @@
"@types/obj-file-parser": "^0.5.0", "@types/obj-file-parser": "^0.5.0",
"@types/pngjs": "^6.0.1", "@types/pngjs": "^6.0.1",
"@types/prompt": "^1.1.2", "@types/prompt": "^1.1.2",
"@types/varint": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^5.9.1", "@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1", "@typescript-eslint/parser": "^5.9.1",
"adm-zip": "^0.5.9", "adm-zip": "^0.5.9",
@ -59,6 +60,7 @@
"jpeg-js": "^0.4.4", "jpeg-js": "^0.4.4",
"pngjs": "^6.0.0", "pngjs": "^6.0.0",
"prismarine-nbt": "^1.6.0", "prismarine-nbt": "^1.6.0",
"twgl.js": "^4.19.1" "twgl.js": "^4.19.1",
"varint-array": "^0.0.0"
} }
} }

View File

@ -3,8 +3,9 @@ import { Schematic } from './schematic_exporter';
import { Litematic } from './litematic_exporter'; import { Litematic } from './litematic_exporter';
import { ASSERT } from '../util'; import { ASSERT } from '../util';
import { ObjExporter } from './obj_exporter'; import { ObjExporter } from './obj_exporter';
import { SchemExporter } from './schem_exporter';
export type TExporters = 'schematic' | 'litematic' | 'obj'; export type TExporters = 'schematic' | 'litematic' | 'obj' | 'schem';
export class ExporterFactory { export class ExporterFactory {
public static GetExporter(voxeliser: TExporters): IExporter { public static GetExporter(voxeliser: TExporters): IExporter {
@ -15,6 +16,8 @@ export class ExporterFactory {
return new Litematic(); return new Litematic();
case 'obj': case 'obj':
return new ObjExporter(); return new ObjExporter();
case 'schem':
return new SchemExporter();
default: default:
ASSERT(false); ASSERT(false);
} }

View File

@ -1,10 +1,9 @@
import { BlockMesh } from '../block_mesh'; import { BlockMesh } from '../block_mesh';
import { Vector3 } from '../vector'; import { Vector3 } from '../vector';
import { IExporter } from './base_exporter'; import { IExporter } from './base_exporter';
import { saveNBT } from '../util/nbt_util';
import fs from 'fs'; import { NBT, TagType } from 'prismarine-nbt';
import { NBT, TagType, writeUncompressed } from 'prismarine-nbt';
import * as zlib from 'zlib';
type BlockID = number; type BlockID = number;
type long = [number, number]; type long = [number, number];
@ -187,17 +186,7 @@ export class Litematic extends IExporter {
this._sizeVector = Vector3.sub(bounds.max, bounds.min).add(1); this._sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
const nbt = this._convertToNBT(blockMesh); const nbt = this._convertToNBT(blockMesh);
saveNBT(nbt, filePath);
const outBuffer = fs.createWriteStream(filePath);
const newBuffer = writeUncompressed(nbt, 'big');
zlib.gzip(newBuffer, (err, buffer) => {
if (!err) {
outBuffer.write(buffer);
outBuffer.end();
}
return err;
});
return false; return false;
} }

View File

@ -0,0 +1,82 @@
import { NBT, TagType } from 'prismarine-nbt';
import { LOG } from '../util';
import { Vector3 } from '../vector';
import { BlockMesh } from '../block_mesh';
import { IExporter } from './base_exporter';
import { saveNBT } from '../util/nbt_util';
const varintarray = require('varint-array');
export class SchemExporter extends IExporter {
public override getFormatFilter() {
return {
name: this.getFormatName(),
extensions: ['schem'],
};
}
public override getFormatName() {
return 'Sponge Schematic';
}
public override getFileExtension(): string {
return 'schem';
}
private static SCHEMA_VERSION = 2;
private static DATA_VERSION = 3105; // 3105 => 1.19, TODO: Remove hardcoded value
public override export(blockMesh: BlockMesh, filePath: string): boolean {
const bounds = blockMesh.getVoxelMesh().getBounds();
const sizeVector = bounds.getDimensions().add(1);
// https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-3.md#paletteObject
// const blockMapping: BlockMapping = {};
const test: {[name: string]: { type: TagType, value: any }} = {
'minecraft:air': { type: TagType.Int, value: 0 },
};
let blockIndex = 1;
for (const blockName of blockMesh.getBlockPalette()) {
const namespacedBlockName = 'minecraft:' + blockName; // FIXME: All block names should be namespaced on import
// ASSERT(!(namespacedBlockName in blockMapping));
// blockMapping[namespacedBlockName] = blockIndex;
test[namespacedBlockName] = { type: TagType.Int, value: blockIndex };
++blockIndex;
}
LOG(test);
// const paletteObject = SchemExporter._createBlockStatePalette(blockMapping);
const blockData = new Array<number>(sizeVector.x * sizeVector.y * sizeVector.z).fill(0);
for (const block of blockMesh.getBlocks()) {
const indexVector = Vector3.sub(block.voxel.position, bounds.min);
const bufferIndex = SchemExporter._getBufferIndex(sizeVector, indexVector);
const namespacedBlockName = 'minecraft:' + block.blockInfo.name;
blockData[bufferIndex] = test[namespacedBlockName].value;
}
const nbt: NBT = {
type: TagType.Compound,
name: 'Schematic',
value: {
Version: { type: TagType.Int, value: SchemExporter.SCHEMA_VERSION },
DataVersion: { type: TagType.Int, value: SchemExporter.DATA_VERSION },
Width: { type: TagType.Short, value: sizeVector.x },
Height: { type: TagType.Short, value: sizeVector.y },
Length: { type: TagType.Short, value: sizeVector.z },
PaletteMax: { type: TagType.Int, value: blockIndex },
Palette: { type: TagType.Compound, value: test },
BlockData: { type: TagType.ByteArray, value: varintarray.encode(blockData) },
},
};
LOG(nbt);
saveNBT(nbt, filePath);
return false;
}
private static _getBufferIndex(dimensions: Vector3, vec: Vector3) {
return vec.x + (vec.z * dimensions.x) + (vec.y * dimensions.x * dimensions.z);
}
}

View File

@ -6,8 +6,8 @@ import { StatusHandler } from '../status';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { NBT, TagType, writeUncompressed } from 'prismarine-nbt'; import { NBT, TagType } from 'prismarine-nbt';
import * as zlib from 'zlib'; import { saveNBT } from '../util/nbt_util';
export class Schematic extends IExporter { export class Schematic extends IExporter {
private _convertToNBT(blockMesh: BlockMesh) { private _convertToNBT(blockMesh: BlockMesh) {
@ -92,17 +92,7 @@ export class Schematic extends IExporter {
this._sizeVector = Vector3.sub(bounds.max, bounds.min).add(1); this._sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
const nbt = this._convertToNBT(blockMesh); const nbt = this._convertToNBT(blockMesh);
saveNBT(nbt, filePath);
const outBuffer = fs.createWriteStream(filePath);
const newBuffer = writeUncompressed(nbt, 'big');
zlib.gzip(newBuffer, (err, buffer) => {
if (!err) {
outBuffer.write(buffer);
outBuffer.end();
}
return err;
});
return false; return false;
} }

View File

@ -171,9 +171,10 @@ export class UI {
label: 'Export', label: 'Export',
elements: { elements: {
'export': new ComboBoxElement<TExporters>('File format', [ 'export': new ComboBoxElement<TExporters>('File format', [
{ id: 'litematic', displayText: 'Litematic' }, { id: 'litematic', displayText: 'Litematic (.litematic)' },
{ id: 'schematic', displayText: 'Schematic' }, { id: 'schematic', displayText: 'Schematic (.schematic)' },
{ id: 'obj', displayText: 'Wavefront OBJ' }, { id: 'obj', displayText: 'Wavefront OBJ (.obj)' },
{ id: 'schem', displayText: 'Sponge Schematic (.schem)' },
]), ]),
}, },
elementsOrder: ['export'], elementsOrder: ['export'],

View File

@ -21,6 +21,12 @@ export class UIMessageBuilder {
} }
} }
public addItem(...messages: string[]) {
for (const message of messages) {
this._messages.push('• ' + message);
}
}
public toString(): string { public toString(): string {
return this._messages.join('<br>'); return this._messages.join('<br>');
} }

21
src/util/nbt_util.ts Normal file
View File

@ -0,0 +1,21 @@
import { ASSERT } from '../util';
import path from 'path';
import { NBT, writeUncompressed } from 'prismarine-nbt';
import zlib from 'zlib';
import fs from 'fs';
export function saveNBT(nbt: NBT, filepath: string) {
ASSERT(path.isAbsolute(filepath), '[saveNBT]: filepath is not absolute');
const outBuffer = fs.createWriteStream(filepath);
const newBuffer = writeUncompressed(nbt, 'big');
zlib.gzip(newBuffer, (err, buffer) => {
if (!err) {
outBuffer.write(buffer);
outBuffer.end();
}
return err;
});
}