forked from mirror/ObjToSchematic
Fixed .litematica exporting
This commit is contained in:
parent
b9aa86d59d
commit
94b76ef0eb
168
index.html
168
index.html
@ -1,104 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>ObjToSchematic</title>
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
<link rel="stylesheet" href="./src/vendor/bootstrap.css">
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>ObjToSchematic</title>
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
<link rel="stylesheet" href="./src/vendor/bootstrap.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<div class="toolbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<input type="file" accept=".obj" class="form-control" id="fileInput">
|
||||
<button id="loadBtn" class="btn btn-primary border-0" type="button">
|
||||
<img src="./resources/svg/load.svg" alt="">
|
||||
Load
|
||||
</button>
|
||||
</div>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<div class="toolbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<input type="file" accept=".obj" class="form-control" id="fileInput">
|
||||
<button id="loadBtn" class="btn btn-primary border-0" type="button">
|
||||
<img src="./resources/svg/load.svg" alt="">
|
||||
Load
|
||||
</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text ">Voxel size</label>
|
||||
<input id="voxelInput" type="number" min="0.1" step="0.1" value="0.1" class="form-control border-0" disabled>
|
||||
<button id="splitBtn" class="btn btn-primary border-0" type="button" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Half voxel size (faster)" disabled>
|
||||
<img src="./resources/svg/split.svg" alt="">
|
||||
</button>
|
||||
<!--
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text ">Voxel size</label>
|
||||
<input id="voxelInput" type="number" min="0.1" step="0.1" value="0.1" class="form-control border-0"
|
||||
disabled>
|
||||
<button id="splitBtn" class="btn btn-primary border-0" type="button" data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom" title="Half voxel size (faster)" disabled>
|
||||
<img src="./resources/svg/split.svg" alt="">
|
||||
</button>
|
||||
<!--
|
||||
<button id="joinBtn" class="btn btn-primary border-0" type="button" disabled>
|
||||
<img src="./resources/svg/join.svg" alt="">
|
||||
</button>
|
||||
-->
|
||||
<button id="voxelBtn" class="btn btn-primary border-0" type="button" disabled>
|
||||
<img src="./resources/svg/voxel.svg" alt="">
|
||||
Voxelise
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="d-grid">
|
||||
<button id="exportBtnDisclaimer" class="btn btn-danger border-0" type="button" disabled>
|
||||
<img src="./resources/svg/save.svg" alt="">
|
||||
Export schematic
|
||||
</button>
|
||||
</div>
|
||||
<button id="voxelBtn" class="btn btn-primary border-0" type="button" disabled>
|
||||
<img src="./resources/svg/voxel.svg" alt="">
|
||||
Voxelise
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast" class="toast canvas-toast align-items-center text-white bg-danger border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div id="toastText" class="toast-body ms-2">
|
||||
toastText
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXPORT DISCLAIMER -->
|
||||
<div id="modalExport" class="modal custom-modal-background fade" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div id="modalExportText" class="modal-body">
|
||||
<p>modalText</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button id="exportBtn" type="button" class="btn btn-danger">
|
||||
<img src="./resources/svg/save.svg" alt="">
|
||||
Export
|
||||
<div class="col">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<button id="exportSchematic" type="button" class="btn btn-danger" disabled>
|
||||
<img src="./resources/svg/save.svg" alt="">
|
||||
Export .schematic
|
||||
</button>
|
||||
<button id="exportLitematic" type="button" class="btn btn-danger" disabled>
|
||||
<img src="./resources/svg/save.svg" alt="">
|
||||
Export .litematic
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GENERAL DISCLAIMER -->
|
||||
<div id="modalGeneral" class="modal custom-modal-background fade" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div id="modalGeneralText" class="modal-body">
|
||||
<p>modalText</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
||||
</div>
|
||||
<div id="toast" class="toast canvas-toast align-items-center text-white bg-danger border-0" role="alert"
|
||||
aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div id="toastText" class="toast-body ms-2">
|
||||
toastText
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXPORT DISCLAIMER -->
|
||||
<div id="modalExport" class="modal custom-modal-background fade" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div id="modalExportText" class="modal-body">
|
||||
<p>modalText</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button id="exportBtn" type="button" class="btn btn-danger">
|
||||
<img src="./resources/svg/save.svg" alt="">
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<!-- GENERAL DISCLAIMER -->
|
||||
<div id="modalGeneral" class="modal custom-modal-background fade" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div id="modalGeneralText" class="modal-body">
|
||||
<p>modalText</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./src/vendor/jquery-3.6.0.min.js"></script>
|
||||
<script src="./src/vendor/jquery-3.3.1.slim.min.js"></script>
|
||||
<script src="./src/vendor/popper.min.js"></script>
|
||||
<script src="./src/vendor/bootstrap.min.js"></script>
|
||||
<script>
|
||||
require("./dist/client.js")
|
||||
</body>
|
||||
|
||||
<script src="./src/vendor/jquery-3.6.0.min.js"></script>
|
||||
<script src="./src/vendor/jquery-3.3.1.slim.min.js"></script>
|
||||
<script src="./src/vendor/popper.min.js"></script>
|
||||
<script src="./src/vendor/bootstrap.min.js"></script>
|
||||
<script>
|
||||
require("./dist/client.js")
|
||||
</script>
|
||||
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 738 KiB After Width: | Height: | Size: 1.6 MiB |
@ -2,7 +2,7 @@ import { Renderer } from "./renderer";
|
||||
import { Mesh } from "./mesh";
|
||||
import { VoxelManager } from "./voxel_manager";
|
||||
import { Vector3 } from "./vector.js";
|
||||
import { Schematic, Litematic } from "./schematic";
|
||||
import { Schematic, Litematic, Exporter } from "./schematic";
|
||||
//const dialog = from 'electron').remote.dialog;
|
||||
import {remote} from 'electron';
|
||||
import * as bootstrap from "bootstrap";
|
||||
@ -13,6 +13,11 @@ enum ToastColour {
|
||||
GREEN = "bg-success"
|
||||
}
|
||||
|
||||
export enum ExportFormat {
|
||||
SCHEMATIC = "schematic",
|
||||
LITEMATIC = "litematic"
|
||||
}
|
||||
|
||||
|
||||
export class AppContext {
|
||||
|
||||
@ -24,7 +29,9 @@ export class AppContext {
|
||||
|
||||
private _toast: bootstrap.Toast;
|
||||
private _modalExport: bootstrap.Modal;
|
||||
//private _modalVoxelise: bootstrap.Modal;
|
||||
private _modalGeneral: bootstrap.Modal;
|
||||
private _cachedFormat?: ExportFormat;
|
||||
|
||||
|
||||
constructor() {
|
||||
@ -41,9 +48,10 @@ export class AppContext {
|
||||
|
||||
this._toast = new bootstrap.Toast(<HTMLElement>document.getElementById('toast'), {delay: 3000});
|
||||
this._modalExport = new bootstrap.Modal(<HTMLElement>document.getElementById('modalExport'), {});
|
||||
//this._modalVoxelise = new bootstrap.Modal(<HTMLElement>document.getElementById('modalVoxelise'), {});
|
||||
this._modalGeneral = new bootstrap.Modal(<HTMLElement>document.getElementById('modalGeneral'), {});
|
||||
|
||||
this._showModalGeneral("Note that in this current version, all blocks in the schematic will be exported as Stone blocks. This will be changed in version 0.4.");
|
||||
//this._showModalGeneral("Note that in this current version, all blocks in the schematic will be exported as Stone blocks. This will be changed in version 0.4.");
|
||||
}
|
||||
|
||||
public load() {
|
||||
@ -61,7 +69,7 @@ export class AppContext {
|
||||
|
||||
try {
|
||||
this._loadedMesh = new Mesh(files[0].path, this._gl);
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
this._showToast(err.message, ToastColour.RED);
|
||||
console.log(err);
|
||||
return;
|
||||
@ -74,7 +82,9 @@ export class AppContext {
|
||||
$('#voxelInput').prop('disabled', false);
|
||||
$('#voxelBtn').prop('disabled', false);
|
||||
$('#splitBtn').prop('disabled', true);
|
||||
$('#exportBtnDisclaimer').prop('disabled', true);
|
||||
|
||||
$('#exportSchematic').prop('disabled', true);
|
||||
$('#exportLitematic').prop('disabled', true);
|
||||
|
||||
this._showToast(`Successfully loaded ${file.name}`, ToastColour.GREEN);
|
||||
}
|
||||
@ -92,6 +102,11 @@ export class AppContext {
|
||||
}
|
||||
*/
|
||||
|
||||
public voxeliseDisclaimer() {
|
||||
//this._modalVoxelise.show();
|
||||
this.voxelise();
|
||||
}
|
||||
|
||||
public voxelise() {
|
||||
const newVoxelSize = $("#voxelInput").prop('value');
|
||||
if (newVoxelSize < 0.001) {
|
||||
@ -112,38 +127,49 @@ export class AppContext {
|
||||
this._renderer.clear();
|
||||
this._renderer.registerVoxelMesh(this._voxelManager);
|
||||
this._renderer.compile();
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
this._showToast(err.message, ToastColour.RED);
|
||||
return;
|
||||
}
|
||||
|
||||
$('#exportBtnDisclaimer').prop('disabled', false);
|
||||
//$('#splitBtn').prop('disabled', false);
|
||||
$('#exportSchematic').prop('disabled', false);
|
||||
$('#exportLitematic').prop('disabled', false);
|
||||
|
||||
this._showToast("Voxelised successfully", ToastColour.GREEN);
|
||||
}
|
||||
|
||||
public exportDisclaimer() {
|
||||
public exportDisclaimer(exportFormat: ExportFormat) {
|
||||
const schematicHeight = Math.ceil(this._voxelManager.maxZ - this._voxelManager.minZ);
|
||||
|
||||
let message = "";
|
||||
if (schematicHeight > 320) {
|
||||
let message = `Note, this schematic is <b>${schematicHeight}</b> blocks tall, this is larger than the height of a Minecraft world (320 in 1.17, 256 in <=1.16).`
|
||||
this._showModalExport(message);
|
||||
} else {
|
||||
this.export();
|
||||
message += `Note, this structure is <b>${schematicHeight}</b> blocks tall, this is larger than the height of a Minecraft world (320 in 1.17, 256 in <=1.16). `;
|
||||
}
|
||||
if (exportFormat == ExportFormat.SCHEMATIC) {
|
||||
message += "Schematic files only support pre-1.13 blocks. As a result, all blocks will be exported as Stone. To export the blocks use the .litematic format with the Litematica mod.";
|
||||
}
|
||||
|
||||
this._cachedFormat = exportFormat;
|
||||
|
||||
if (message.length == 0) {
|
||||
this.export();
|
||||
} else {
|
||||
this._showModalExport(message);
|
||||
}
|
||||
}
|
||||
|
||||
public export() {
|
||||
this._modalExport.hide();
|
||||
|
||||
const filePath = remote.dialog.showSaveDialogSync({
|
||||
title: "Save schematic",
|
||||
title: "Save structure",
|
||||
buttonLabel: "Save",
|
||||
filters: [{
|
||||
filters: this._cachedFormat == ExportFormat.SCHEMATIC ? [{
|
||||
name: 'Schematic',
|
||||
extensions: ['schematic']
|
||||
}] : [{
|
||||
name: 'Litematic',
|
||||
extensions: ['litematic']
|
||||
}]
|
||||
});
|
||||
|
||||
@ -153,15 +179,20 @@ export class AppContext {
|
||||
}
|
||||
|
||||
try {
|
||||
const schematic = new Litematic(this._voxelManager);
|
||||
schematic.exportSchematic(filePath);
|
||||
let exporter: Exporter;
|
||||
if (this._cachedFormat == ExportFormat.SCHEMATIC) {
|
||||
exporter = new Schematic(this._voxelManager);
|
||||
} else {
|
||||
exporter = new Litematic(this._voxelManager);
|
||||
}
|
||||
exporter.export(filePath);
|
||||
} catch (err) {
|
||||
this._showToast("Failed to export schematic", ToastColour.RED);
|
||||
this._showToast("Failed to export", ToastColour.RED);
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
this._showToast("Successfully saved schematic", ToastColour.GREEN);
|
||||
this._showToast("Successfully saved", ToastColour.GREEN);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { AppContext } from "./app_context";
|
||||
import { ExportFormat } from "./app_context";
|
||||
|
||||
const context = new AppContext();
|
||||
|
||||
@ -9,7 +10,7 @@ $("#loadBtn").on("click", () => {
|
||||
|
||||
|
||||
$("#voxelBtn").on("click", () => {
|
||||
context.voxelise();
|
||||
context.voxeliseDisclaimer();
|
||||
});
|
||||
|
||||
|
||||
@ -20,15 +21,20 @@ $("#splitBtn").on("click", () => {
|
||||
*/
|
||||
|
||||
|
||||
$("#exportBtnDisclaimer").on("click", async () => {
|
||||
context.exportDisclaimer();
|
||||
});
|
||||
|
||||
$("#exportBtn").on("click", async () => {
|
||||
context.export();
|
||||
});
|
||||
|
||||
|
||||
$("#exportSchematic").on("click", async () => {
|
||||
context.exportDisclaimer(ExportFormat.SCHEMATIC);
|
||||
});
|
||||
|
||||
$("#exportLitematic").on("click", async () => {
|
||||
context.exportDisclaimer(ExportFormat.LITEMATIC);
|
||||
});
|
||||
|
||||
|
||||
function render(time: number) {
|
||||
context.draw();
|
||||
requestAnimationFrame(render);
|
||||
|
231
src/schematic.ts
231
src/schematic.ts
@ -1,34 +1,63 @@
|
||||
import zlib from "zlib";
|
||||
import fs from "fs";
|
||||
import * as zlib from "zlib";
|
||||
import * as fs from "fs";
|
||||
import { NBT, TagType, writeUncompressed } from "prismarine-nbt";
|
||||
import { Vector3 } from "./vector";
|
||||
import { VoxelManager } from "./voxel_manager";
|
||||
import { Block } from "./block_atlas";
|
||||
import { powerMonitor } from "electron";
|
||||
|
||||
|
||||
export class Schematic {
|
||||
export abstract class Exporter {
|
||||
|
||||
private _sizeVector: Vector3;
|
||||
private _schematic: NBT;
|
||||
protected _voxelManager: VoxelManager
|
||||
protected _minPos: Vector3
|
||||
protected _maxPos: Vector3
|
||||
protected _sizeVector: Vector3
|
||||
|
||||
constructor(voxelManager: VoxelManager) {
|
||||
const minPos = new Vector3(voxelManager.minX, voxelManager.minY, voxelManager.minZ);
|
||||
const maxPos = new Vector3(voxelManager.maxX, voxelManager.maxY, voxelManager.maxZ);
|
||||
this._voxelManager = voxelManager;
|
||||
|
||||
this._minPos = new Vector3(voxelManager.minX, voxelManager.minY, voxelManager.minZ);
|
||||
this._maxPos = new Vector3(voxelManager.maxX, voxelManager.maxY, voxelManager.maxZ);
|
||||
this._sizeVector = Vector3.sub(this._maxPos, this._minPos).addScalar(1);
|
||||
console.log(this._minPos, this._maxPos);
|
||||
}
|
||||
|
||||
abstract convertToNBT(): NBT
|
||||
|
||||
export(filePath: string): boolean {
|
||||
const nbt = this.convertToNBT();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Schematic extends Exporter {
|
||||
|
||||
convertToNBT() {
|
||||
|
||||
this._sizeVector = Vector3.addScalar(Vector3.sub(maxPos, minPos), 1);
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
|
||||
let blocksData = Array<number>(bufferSize);
|
||||
voxelManager.voxels.forEach(voxel => {
|
||||
//console.log(voxel);
|
||||
const indexVector = Vector3.sub(voxel.position, minPos);
|
||||
const index = this._getBufferIndex(indexVector);
|
||||
//this._schematic.value.Blocks.value[index] = 1;
|
||||
this._voxelManager.voxels.forEach(voxel => {
|
||||
const indexVector = Vector3.sub(voxel.position, this._minPos);
|
||||
const index = this._getBufferIndex(indexVector, this._sizeVector);
|
||||
blocksData[index] = Block.Stone;
|
||||
});
|
||||
|
||||
this._schematic = {
|
||||
const nbt: NBT = {
|
||||
type: TagType.Compound,
|
||||
name: 'Schematic',
|
||||
value: {
|
||||
@ -42,110 +71,121 @@ export class Schematic {
|
||||
TileEntities: { type: TagType.List, value: { type: TagType.Int, value: Array(0) } }
|
||||
}
|
||||
};
|
||||
|
||||
return nbt;
|
||||
}
|
||||
|
||||
_getBufferIndex(vec: Vector3) {
|
||||
return (this._sizeVector.z * this._sizeVector.x * vec.y) + (this._sizeVector.x * vec.z) + vec.x;
|
||||
}
|
||||
|
||||
exportSchematic(filePath: string) {
|
||||
const outBuffer = fs.createWriteStream(filePath);
|
||||
const newBuffer = writeUncompressed(this._schematic, "big");
|
||||
|
||||
zlib.gzip(newBuffer, (err, buffer) => {
|
||||
if (!err) {
|
||||
outBuffer.write(buffer);
|
||||
outBuffer.end(() => console.log('Written!'));
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
_getBufferIndex(vec: Vector3, sizeVector: Vector3) {
|
||||
return (sizeVector.z * sizeVector.x * vec.y) + (sizeVector.x * vec.z) + vec.x;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type BlockID = number;
|
||||
type long = [number, number];
|
||||
|
||||
interface BlockMapping {
|
||||
[name: string]: BlockID
|
||||
}
|
||||
|
||||
export class Litematic {
|
||||
|
||||
private _sizeVector: Vector3;
|
||||
private _litematic: NBT;
|
||||
export class Litematic extends Exporter {
|
||||
|
||||
// XZY
|
||||
_getBufferIndex(vec: Vector3) {
|
||||
return (this._sizeVector.z * this._sizeVector.x * vec.y) + (this._sizeVector.x * vec.z) + vec.x;
|
||||
}
|
||||
|
||||
constructor(voxelManager: VoxelManager) {
|
||||
const minPos = new Vector3(voxelManager.minX, voxelManager.minY, voxelManager.minZ);
|
||||
const maxPos = new Vector3(voxelManager.maxX, voxelManager.maxY, voxelManager.maxZ);
|
||||
this._sizeVector = Vector3.sub(maxPos, minPos).addScalar(1);
|
||||
console.log("sizeVector", this._sizeVector);
|
||||
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
let buffer = Array<number>(bufferSize);
|
||||
for (let i = 0; i < bufferSize; ++i) {
|
||||
buffer[i] = 0;
|
||||
}
|
||||
|
||||
const blockPalette = voxelManager.blockPalette;
|
||||
let blockMapping: { [name: string]: number } = {"air": 0};
|
||||
_createBlockMapping(): BlockMapping {
|
||||
const blockPalette = this._voxelManager.blockPalette;
|
||||
|
||||
let blockMapping: BlockMapping = {"air": 0};
|
||||
for (let i = 0; i < blockPalette.length; ++i) {
|
||||
const blockName = blockPalette[i];
|
||||
blockMapping[blockName] = i + 1; // Ensure 0 maps to air
|
||||
}
|
||||
console.log(blockMapping);
|
||||
|
||||
const paletteSize = blockPalette.length + 1;
|
||||
const stride = (paletteSize - 1).toString(2).length;
|
||||
const numBits = stride * bufferSize;
|
||||
const numLongs = Math.ceil(numBits / 64);
|
||||
console.log("numLongs", numLongs);
|
||||
console.log("stride", stride);
|
||||
return blockMapping;
|
||||
}
|
||||
|
||||
voxelManager.voxels.forEach(voxel => {
|
||||
const indexVector = Vector3.sub(voxel.position, minPos);
|
||||
_createBlockBuffer(blockMapping: BlockMapping): Array<BlockID> {
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
console.log(this._sizeVector);
|
||||
|
||||
let buffer = Array<BlockID>(bufferSize).fill(0);
|
||||
this._voxelManager.voxels.forEach(voxel => {
|
||||
const indexVector = Vector3.sub(voxel.position, this._minPos);
|
||||
const index = this._getBufferIndex(indexVector);
|
||||
buffer[index] = blockMapping[voxel.block];
|
||||
buffer[index] = blockMapping[voxel.block || "air"];
|
||||
});
|
||||
|
||||
let blockStates: [number, number][] = [];
|
||||
for (let i = 0; i < numLongs; ++i) {
|
||||
blockStates.push([0, 0]);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
let str = "";
|
||||
for (let i = 0; i < bufferSize; ++i) {
|
||||
str = buffer[i].toString(2).padStart(stride, "0") + str;
|
||||
}
|
||||
let a = Math.ceil(str.length / 64) * 64;
|
||||
str = str.padStart(a, "0");
|
||||
_createBlockStates(blockMapping: BlockMapping) {
|
||||
const blockEncoding = this._encodeBlockBuffer(blockMapping);
|
||||
|
||||
let j = 0;
|
||||
for (let i = str.length; i > 0; i -= 64) {
|
||||
let right = parseInt(str.substring(i-32, i), 2);
|
||||
let left = parseInt(str.substring(i-64, i-32), 2);
|
||||
if (right > Math.pow(2, 30)) {
|
||||
right = -((right << 1) >> 1);
|
||||
|
||||
let blockStates = new Array<long>();
|
||||
|
||||
for (let i = blockEncoding.length; i > 0; i -= 64) {
|
||||
let right = parseInt(blockEncoding.substring(i-32, i), 2);
|
||||
let left = parseInt(blockEncoding.substring(i-64, i-32), 2);
|
||||
|
||||
// TODO: Cleanup, UINT32 -> INT32
|
||||
if (right > 2147483647) {
|
||||
//right = -(-right & 0xFFFFFFFF);
|
||||
right -= 4294967296;
|
||||
}
|
||||
if (left > Math.pow(2, 30)) {
|
||||
left = -((left << 1) >> 1);
|
||||
if (left > 2147483647) {
|
||||
//left = -(-left & 0xFFFFFFFF);
|
||||
left -= 4294967296;
|
||||
}
|
||||
blockStates[j] = [left, right];
|
||||
++j;
|
||||
}
|
||||
console.log(blockStates);
|
||||
|
||||
let blockStatePalette = Array(paletteSize);
|
||||
for (const block of blockPalette) {
|
||||
let index = blockMapping[block];
|
||||
let blockName = "minecraft:" + block;
|
||||
blockStates.push([left, right]);
|
||||
}
|
||||
|
||||
return blockStates;
|
||||
}
|
||||
|
||||
_encodeBlockBuffer(blockMapping: BlockMapping) {
|
||||
let blockBuffer = this._createBlockBuffer(blockMapping);
|
||||
|
||||
const paletteSize = Object.keys(blockMapping).length;
|
||||
let stride = (paletteSize - 1).toString(2).length;
|
||||
stride = Math.max(2, stride);
|
||||
|
||||
let encoding = "";
|
||||
for (let i = blockBuffer.length - 1; i >= 0; --i) {
|
||||
encoding += blockBuffer[i].toString(2).padStart(stride, "0");
|
||||
}
|
||||
|
||||
const requiredLength = Math.ceil(encoding.length / 64) * 64;
|
||||
encoding = encoding.padStart(requiredLength, "0");
|
||||
|
||||
return encoding;
|
||||
}
|
||||
|
||||
_createBlockStatePalette(blockMapping: BlockMapping) {
|
||||
let blockStatePalette = Array(Object.keys(blockMapping).length);
|
||||
for (const block of Object.keys(blockMapping)) {
|
||||
const index = blockMapping[block];
|
||||
const blockName = "minecraft:" + block;
|
||||
blockStatePalette[index] = { Name: { type: TagType.String, value: blockName } };
|
||||
}
|
||||
blockStatePalette[0] = { Name: { type: TagType.String, value: "minecraft:air" } };
|
||||
|
||||
this._litematic = {
|
||||
return blockStatePalette;
|
||||
}
|
||||
|
||||
convertToNBT() {
|
||||
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
const blockMapping = this._createBlockMapping();
|
||||
|
||||
const blockStates = this._createBlockStates(blockMapping);
|
||||
const blockStatePalette = this._createBlockStatePalette(blockMapping);
|
||||
|
||||
const nbt: NBT = {
|
||||
type: TagType.Compound,
|
||||
name: 'Litematic',
|
||||
value: {
|
||||
@ -164,7 +204,7 @@ export class Litematic {
|
||||
RegionCount: { type: TagType.Int, value: 1 },
|
||||
TimeCreated: { type: TagType.Long, value: [0, 0] },
|
||||
TimeModified: { type: TagType.Long, value: [0, 0] },
|
||||
TotalBlocks: { type: TagType.Int, value: voxelManager.voxels.length },
|
||||
TotalBlocks: { type: TagType.Int, value: this._voxelManager.voxels.length },
|
||||
TotalVolume: { type: TagType.Int, value: bufferSize },
|
||||
},
|
||||
},
|
||||
@ -200,21 +240,8 @@ export class Litematic {
|
||||
Version: { type: TagType.Int, value: 5 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exportSchematic(filePath: string) {
|
||||
const outBuffer = fs.createWriteStream(filePath);
|
||||
const newBuffer = writeUncompressed(this._litematic, "big");
|
||||
|
||||
zlib.gzip(newBuffer, (err, buffer) => {
|
||||
if (!err) {
|
||||
outBuffer.write(buffer);
|
||||
outBuffer.end(() => console.log('Written!'));
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
return nbt;
|
||||
}
|
||||
|
||||
}
|
18
src/test.js
Normal file
18
src/test.js
Normal file
@ -0,0 +1,18 @@
|
||||
let val = 2147483649;
|
||||
|
||||
function fa(val) {
|
||||
if (val > 2147483647) {
|
||||
return -(-val & 0xFFFFFFFF);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function fb(val) {
|
||||
if (val > 2147483647) {
|
||||
return val - 4294967296;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
console.log(fa(val), fb(val));
|
@ -9,7 +9,8 @@ import { Mesh, MaterialType } from "./mesh";
|
||||
|
||||
interface Block {
|
||||
position: Vector3;
|
||||
block: string
|
||||
colours: Array<RGB>;
|
||||
block?: string
|
||||
}
|
||||
|
||||
|
||||
@ -107,6 +108,25 @@ export class VoxelManager {
|
||||
return this.voxelsHash.contains(pos);
|
||||
}
|
||||
|
||||
assignBlocks() {
|
||||
this.blockPalette = [];
|
||||
|
||||
for (let i = 0; i < this.voxels.length; ++i) {
|
||||
let averageColour = this.voxels[i].colours.reduce((a, c) => {return {r: a.r + c.r, g: a.g + c.g, b: a.b + c.b}})
|
||||
let n = this.voxels[i].colours.length;
|
||||
averageColour.r /= n;
|
||||
averageColour.g /= n;
|
||||
averageColour.b /= n;
|
||||
const block = this.blockAtlas.getBlock(averageColour);
|
||||
this.voxels[i].block = block.name;
|
||||
this.voxelTexcoords.push(block.texcoord);
|
||||
|
||||
if (!this.blockPalette.includes(block.name)) {
|
||||
this.blockPalette.push(block.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addVoxel(vec: Vector3, block: BlockInfo) {
|
||||
|
||||
// (0.5, 0.5, 0.5) -> (0, 0, 0);
|
||||
@ -124,15 +144,25 @@ export class VoxelManager {
|
||||
// Convert to
|
||||
const pos = this._toGridPosition(vec);
|
||||
if (this.voxelsHash.contains(pos)) {
|
||||
return;
|
||||
for (let i = 0; i < this.voxels.length; ++i) {
|
||||
if (this.voxels[i].position.equals(pos)) {
|
||||
this.voxels[i].colours.push(block.colour);
|
||||
//console.log("Overlap");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
this.voxels.push({position: pos, colours: [block.colour]});
|
||||
//this.voxelTexcoords.push(block.texcoord);
|
||||
this.voxelsHash.add(pos);
|
||||
}
|
||||
|
||||
/*
|
||||
if (!this.blockPalette.includes(block.name)) {
|
||||
this.blockPalette.push(block.name);
|
||||
}
|
||||
this.voxels.push({position: pos, block: block.name});
|
||||
this.voxelTexcoords.push(block.texcoord);
|
||||
this.voxelsHash.add(pos);
|
||||
*/
|
||||
|
||||
this.minX = Math.min(this.minX, pos.x);
|
||||
this.minY = Math.min(this.minY, pos.y);
|
||||
@ -356,9 +386,9 @@ export class VoxelManager {
|
||||
} else {
|
||||
// We've reached the voxel level, stop
|
||||
const voxelColour = this._getVoxelColour(triangle, aabb.centre);
|
||||
const blockTexcoord = this.blockAtlas.getBlock(voxelColour);
|
||||
const block = this.blockAtlas.getBlock(voxelColour);
|
||||
|
||||
this.addVoxel(aabb.centre, blockTexcoord);
|
||||
this.addVoxel(aabb.centre, block);
|
||||
triangleAABBs.push(aabb);
|
||||
}
|
||||
}
|
||||
@ -383,6 +413,7 @@ export class VoxelManager {
|
||||
this.voxeliseTriangle(triangle);
|
||||
}
|
||||
}
|
||||
this.assignBlocks();
|
||||
console.log(this.blockPalette);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user