Added prototype .litematic support

This commit is contained in:
Lucas Dower 2021-08-21 00:09:53 +01:00
parent bb607353e5
commit b9aa86d59d
6 changed files with 202 additions and 60 deletions

View File

@ -2,7 +2,7 @@ import { Renderer } from "./renderer";
import { Mesh } from "./mesh";
import { VoxelManager } from "./voxel_manager";
import { Vector3 } from "./vector.js";
import { Schematic } from "./schematic";
import { Schematic, Litematic } from "./schematic";
//const dialog = from 'electron').remote.dialog;
import {remote} from 'electron';
import * as bootstrap from "bootstrap";
@ -153,7 +153,7 @@ export class AppContext {
}
try {
const schematic = new Schematic(this._voxelManager);
const schematic = new Litematic(this._voxelManager);
schematic.exportSchematic(filePath);
} catch (err) {
this._showToast("Failed to export schematic", ToastColour.RED);

View File

@ -5,7 +5,7 @@ import { UV, RGB } from "./util";
import fs from "fs";
import path from "path";
interface BlockInfo {
export interface BlockInfo {
name: string;
colour: RGB;
texcoord: UV
@ -37,12 +37,8 @@ export class BlockAtlas {
this._blocks = blocksJSON;
}
public getTexcoord(voxelColour: RGB) {
const block = this._getBlock(voxelColour);
return block.texcoord;
}
private _getBlock(voxelColour: RGB): BlockInfo {
public getBlock(voxelColour: RGB): BlockInfo {
const voxelColourVector = new Vector3(voxelColour.r, voxelColour.g, voxelColour.b);
let cachedBlockIndex = this._cachedBlocks.get(voxelColourVector);

View File

@ -153,7 +153,7 @@ export class Renderer {
if (this._debug) {
voxelManager.voxels.forEach((voxel) => {
this.registerBox(voxel);
this.registerBox(voxel.position);
});
} else {
// Setup arrays for calculating voxel ambient occlusion
@ -162,7 +162,7 @@ export class Renderer {
const voxel = voxelManager.voxels[i];
//const colour = voxelManager.voxelColours[i];
const texcoord = voxelManager.voxelTexcoords[i];
this._registerVoxel(voxel, voxelManager, texcoord);
this._registerVoxel(voxel.position, voxelManager, texcoord);
}
/*
voxelManager.voxels.forEach((voxel) => {

View File

@ -1,23 +1,10 @@
import zlib from "zlib";
import fs from "fs";
import { byte, NBT, parse, short, TagType, writeUncompressed } from "prismarine-nbt";
import { NBT, TagType, writeUncompressed } from "prismarine-nbt";
import { Vector3 } from "./vector";
import { remote } from "electron"
import { VoxelManager } from "./voxel_manager";
import { Block } from "./block_atlas";
//const zlib = require("zlib");
//const fs = require('fs');
//const { parse, writeUncompressed } = require('prismarine-nbt');
//const { Vector3 } = require('./vector.js');
//const dialog = require('electron').remote.dialog;
/*
interface SchematicValue<T> {
type: string,
value: T
}
*/
import { powerMonitor } from "electron";
export class Schematic {
@ -26,20 +13,16 @@ export class Schematic {
private _schematic: NBT;
constructor(voxelManager: VoxelManager) {
//const minPos = voxelManager._voxelCentreToPosition(new Vector3(voxelManager.minX, voxelManager.minY, voxelManager.minZ));
//const maxPos = voxelManager._voxelCentreToPosition(new Vector3(voxelManager.maxX, voxelManager.maxY, voxelManager.maxZ));
//console.log("SAMPLE", voxelManager.voxels[0]);
const minPos = new Vector3(voxelManager.minX, voxelManager.minY, voxelManager.minZ);
const maxPos = new Vector3(voxelManager.maxX, voxelManager.maxY, voxelManager.maxZ);
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, minPos);
const indexVector = Vector3.sub(voxel.position, minPos);
const index = this._getBufferIndex(indexVector);
//this._schematic.value.Blocks.value[index] = 1;
blocksData[index] = Block.Stone;
@ -49,27 +32,16 @@ export class Schematic {
type: TagType.Compound,
name: 'Schematic',
value: {
Width: { type: TagType.Short, value: this._sizeVector.x },
Height: { type: TagType.Short, value: this._sizeVector.y },
Length: { type: TagType.Short, value: this._sizeVector.z },
Materials: { type: TagType.String, value: 'Alpha' },
Blocks: { type: TagType.ByteArray, value: blocksData },
Data: { type: TagType.ByteArray, value: new Array<number>(bufferSize).fill(0) },
Entities: { type: TagType.List, value: {type: TagType.Int, value: Array(0)} },
TileEntities: { type: TagType.List, value: {type: TagType.Int, value: Array(0)} }
Width: { type: TagType.Short, value: this._sizeVector.x },
Height: { type: TagType.Short, value: this._sizeVector.y },
Length: { type: TagType.Short, value: this._sizeVector.z },
Materials: { type: TagType.String, value: 'Alpha' },
Blocks: { type: TagType.ByteArray, value: blocksData },
Data: { type: TagType.ByteArray, value: new Array<number>(bufferSize).fill(0) },
Entities: { type: TagType.List, value: { type: TagType.Int, value: Array(0) } },
TileEntities: { type: TagType.List, value: { type: TagType.Int, value: Array(0) } }
}
};
/*
for (let i = 0; i < voxelManager.voxels.length; ++i) {
const voxel = voxelManager.voxels[i];
const pos = voxelManager._voxelCentreToPosition(voxel);
const indexVector = Vector3.sub(pos, minPos);
const index = this._getBufferIndex(indexVector);
this.schematic.value.Blocks.value[index] = 1;
}
*/
}
_getBufferIndex(vec: Vector3) {
@ -79,12 +51,166 @@ export class Schematic {
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;
}
});
}
}
export class Litematic {
private _sizeVector: Vector3;
private _litematic: NBT;
// 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};
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);
voxelManager.voxels.forEach(voxel => {
const indexVector = Vector3.sub(voxel.position, minPos);
const index = this._getBufferIndex(indexVector);
buffer[index] = blockMapping[voxel.block];
});
let blockStates: [number, number][] = [];
for (let i = 0; i < numLongs; ++i) {
blockStates.push([0, 0]);
}
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");
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);
}
if (left > Math.pow(2, 30)) {
left = -((left << 1) >> 1);
}
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;
blockStatePalette[index] = { Name: { type: TagType.String, value: blockName } };
}
blockStatePalette[0] = { Name: { type: TagType.String, value: "minecraft:air" } };
this._litematic = {
type: TagType.Compound,
name: 'Litematic',
value: {
Metadata: {
type: TagType.Compound, value: {
Author: { type: TagType.String, value: "" },
Description: { type: TagType.String, value: "" },
Size: {
type: TagType.Compound, value: {
x: { type: TagType.Int, value: this._sizeVector.x },
y: { type: TagType.Int, value: this._sizeVector.y },
z: { type: TagType.Int, value: this._sizeVector.z },
}
},
Name: { type: TagType.String, value: "" },
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 },
TotalVolume: { type: TagType.Int, value: bufferSize },
},
},
Regions: {
type: TagType.Compound, value: {
Unnamed: {
type: TagType.Compound, value: {
BlockStates: { type: TagType.LongArray, value: blockStates },
PendingBlockTicks: { type: TagType.List, value: { type: TagType.Int, value: [] } },
Position: {
type: TagType.Compound, value: {
x: { type: TagType.Int, value: 0 },
y: { type: TagType.Int, value: 0 },
z: { type: TagType.Int, value: 0 },
}
},
BlockStatePalette: { type: TagType.List, value: { type: TagType.Compound, value: blockStatePalette } },
Size: {
type: TagType.Compound, value: {
x: { type: TagType.Int, value: this._sizeVector.x },
y: { type: TagType.Int, value: this._sizeVector.y },
z: { type: TagType.Int, value: this._sizeVector.z },
}
},
PendingFluidTicks: { type: TagType.List, value: { type: TagType.Int, value: [] } },
TileEntities: { type: TagType.List, value: { type: TagType.Int, value: [] } },
Entities: { type: TagType.List, value: { type: TagType.Int, value: [] } }
}
}
},
},
MinecraftDataVersion: { type: TagType.Int, value: 2730 },
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;
}

View File

@ -40,6 +40,13 @@ export class Vector3 extends Hashable {
);
}
addScalar(scalar: number) {
this.x += scalar;
this.y += scalar;
this.z += scalar;
return this;
}
static sub(vecA: Vector3, vecB: Vector3) {
return new Vector3(
vecA.x - vecB.x,

View File

@ -2,11 +2,16 @@ import { CubeAABB } from "./aabb";
import { Vector3 } from "./vector.js";
import { HashSet } from "./hash_map";
import { Texture } from "./texture";
import { BlockAtlas } from "./block_atlas";
import { BlockAtlas, BlockInfo } from "./block_atlas";
import { UV, RGB } from "./util";
import { Triangle } from "./triangle";
import { Mesh, MaterialType } from "./mesh";
interface Block {
position: Vector3;
block: string
}
interface TriangleCubeAABBs {
triangle: Triangle;
@ -15,7 +20,7 @@ interface TriangleCubeAABBs {
export class VoxelManager {
public voxels: Array<Vector3>;
public voxels: Array<Block>;
public voxelTexcoords: Array<UV>;
public triangleAABBs: Array<TriangleCubeAABBs>;
public _voxelSize: number;
@ -25,6 +30,7 @@ export class VoxelManager {
private _blockMode!: MaterialType;
private _currentTexture!: Texture;
private _currentColour!: RGB;
public blockPalette: Array<string>;
public minX = Infinity; public maxX = -Infinity;
public minY = Infinity; public maxY = -Infinity;
@ -38,6 +44,7 @@ export class VoxelManager {
this.voxelsHash = new HashSet(2048);
this.blockAtlas = new BlockAtlas();
this.blockPalette = [];
}
public setVoxelSize(voxelSize: number) {
@ -47,6 +54,7 @@ export class VoxelManager {
private _clearVoxels() {
this.voxels = [];
this.voxelTexcoords = [];
this.blockPalette = []
this.minX = Infinity;
this.minY = Infinity;
@ -99,7 +107,7 @@ export class VoxelManager {
return this.voxelsHash.contains(pos);
}
addVoxel(vec: Vector3, blockTexcoord: UV) {
addVoxel(vec: Vector3, block: BlockInfo) {
// (0.5, 0.5, 0.5) -> (0, 0, 0);
//console.log(vec);
@ -118,8 +126,12 @@ export class VoxelManager {
if (this.voxelsHash.contains(pos)) {
return;
}
this.voxels.push(pos);
this.voxelTexcoords.push(blockTexcoord);
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);
@ -253,9 +265,9 @@ export class VoxelManager {
for (const sub of AABB.subdivide()) {
if (triangle.intersectAABB(sub)) {
const voxelColour = this._getVoxelColour(triangle, sub.centre);
const blockTexcoord = this.blockAtlas.getTexcoord(voxelColour);
const block = this.blockAtlas.getBlock(voxelColour);
this.addVoxel(sub.centre, blockTexcoord);
this.addVoxel(sub.centre, block);
triangleAABBs.push(sub);
}
}
@ -344,7 +356,7 @@ export class VoxelManager {
} else {
// We've reached the voxel level, stop
const voxelColour = this._getVoxelColour(triangle, aabb.centre);
const blockTexcoord = this.blockAtlas.getTexcoord(voxelColour);
const blockTexcoord = this.blockAtlas.getBlock(voxelColour);
this.addVoxel(aabb.centre, blockTexcoord);
triangleAABBs.push(aabb);
@ -371,6 +383,7 @@ export class VoxelManager {
this.voxeliseTriangle(triangle);
}
}
console.log(this.blockPalette);
}
}