Added OtS_BlockMesh and updated exporters to use

This commit is contained in:
Lucas Dower 2023-10-14 00:50:57 +01:00
parent c51c4d900c
commit 2754ecf3f6
8 changed files with 201 additions and 61 deletions

View File

@ -1,4 +1,5 @@
import { BlockMesh } from '../block_mesh';
import { OtS_BlockMesh } from '../ots_block_mesh';
export type TStructureRegion = { name: string, content: Uint8Array };
@ -20,5 +21,5 @@ export abstract class IExporter {
* @param blockMesh The block mesh to export.
* @param filePath The location to save the file to.
*/
public abstract export(blockMesh: BlockMesh): TStructureExport;
public abstract export(blockMesh: OtS_BlockMesh): TStructureExport;
}

View File

@ -1,4 +1,5 @@
import { BlockMesh } from '../block_mesh';
import { OtS_BlockMesh } from '../ots_block_mesh';
import { IExporter, TStructureExport } from './base_exporter';
export class IndexedJSONExporter extends IExporter {
@ -9,10 +10,10 @@ export class IndexedJSONExporter extends IExporter {
};
}
public override export(blockMesh: BlockMesh): TStructureExport {
public override export(blockMesh: OtS_BlockMesh): TStructureExport {
const blocks = blockMesh.getBlocks();
const blocksUsed = blockMesh.getBlockPalette();
const blocksUsed = Array.from(blockMesh.calcBlocksUsed());
const blockToIndex = new Map<string, number>();
const indexToBlock = new Map<number, string>();
for (let i = 0; i < blocksUsed.length; ++i) {
@ -23,10 +24,8 @@ export class IndexedJSONExporter extends IExporter {
const blockArray = new Array<Array<number>>();
// Serialise all block except for the last one.
for (let i = 0; i < blocks.length; ++i) {
const block = blocks[i];
const pos = block.voxel.position;
blockArray.push([pos.x, pos.y, pos.z, blockToIndex.get(block.blockInfo.name)!]);
for (const { position, name } of blockMesh.getBlocks()) {
blockArray.push([position.x, position.y, position.z, blockToIndex.get(name)!]);
}
const json = JSON.stringify({

View File

@ -8,6 +8,7 @@ import { ASSERT } from '../util/error_util';
import { saveNBT } from '../util/nbt_util';
import { Vector3 } from '../vector';
import { IExporter, TStructureExport } from './base_exporter';
import { OtS_BlockMesh } from '../ots_block_mesh';
type BlockID = number;
type long = [number, number];
@ -21,7 +22,7 @@ export class Litematic extends IExporter {
};
}
public override export(blockMesh: BlockMesh): TStructureExport {
public override export(blockMesh: OtS_BlockMesh): TStructureExport {
const nbt = this._convertToNBT(blockMesh);
return { type: 'single', extension: '.litematic', content: saveNBT(nbt) };
}
@ -29,12 +30,14 @@ export class Litematic extends IExporter {
/**
* Create a mapping from block names to their respecitve index in the block state palette.
*/
private _createBlockMapping(blockMesh: BlockMesh): BlockMapping {
private _createBlockMapping(blockMesh: OtS_BlockMesh): BlockMapping {
const blockMapping: BlockMapping = new Map();
blockMapping.set('minecraft:air', 0);
blockMesh.getBlockPalette().forEach((blockName, index) => {
blockMapping.set(blockName, index + 1);
let index = 1;
blockMesh.calcBlocksUsed().forEach((blockName) => {
blockMapping.set(blockName, index);
++index;
});
return blockMapping;
@ -43,26 +46,26 @@ export class Litematic extends IExporter {
/**
* Pack the blocks into a buffer that's the dimensions of the block mesh.
*/
private _createBlockBuffer(blockMesh: BlockMesh, blockMapping: BlockMapping): Uint32Array {
const bounds = blockMesh.getVoxelMesh()?.getBounds();
private _createBlockBuffer(blockMesh: OtS_BlockMesh, blockMapping: BlockMapping): Uint32Array {
const bounds = blockMesh.getBounds();
const sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
const buffer = new Uint32Array(sizeVector.x * sizeVector.y * sizeVector.z);
blockMesh.getBlocks().forEach((block) => {
const indexVector = Vector3.sub(block.voxel.position, bounds.min);
for (const { position, name } of blockMesh.getBlocks()) {
const indexVector = Vector3.sub(position, bounds.min);
const bufferIndex = (sizeVector.z * sizeVector.x * indexVector.y) + (sizeVector.x * indexVector.z) + indexVector.x; // XZY ordering
const mappingIndex = blockMapping.get(block.blockInfo.name);
const mappingIndex = blockMapping.get(name);
ASSERT(mappingIndex !== undefined, 'Invalid mapping index');
buffer[bufferIndex] = mappingIndex;
});
};
return buffer;
}
private _createBlockStates(blockMesh: BlockMesh, blockMapping: BlockMapping) {
private _createBlockStates(blockMesh: OtS_BlockMesh, blockMapping: BlockMapping) {
const buffer = this._encodeBlockBuffer(blockMesh, blockMapping);
const numBytes = buffer.length;
@ -96,7 +99,7 @@ export class Litematic extends IExporter {
return blockStates;
}
private _encodeBlockBuffer(blockMesh: BlockMesh, blockMapping: BlockMapping) {
private _encodeBlockBuffer(blockMesh: OtS_BlockMesh, blockMapping: BlockMapping) {
const blockBuffer = this._createBlockBuffer(blockMesh, blockMapping);
const paletteSize = blockMapping.size;
@ -156,8 +159,8 @@ export class Litematic extends IExporter {
return blockStatePalette;
}
private _convertToNBT(blockMesh: BlockMesh) {
const bounds = blockMesh.getVoxelMesh()?.getBounds();
private _convertToNBT(blockMesh: OtS_BlockMesh) {
const bounds = blockMesh.getBounds();
const sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
const bufferSize = sizeVector.x * sizeVector.y * sizeVector.z;
@ -165,7 +168,7 @@ export class Litematic extends IExporter {
const blockStates = this._createBlockStates(blockMesh, blockMapping);
const blockStatePalette = this._createBlockStatePalette(blockMapping);
const numBlocks = blockMesh.getBlocks().length;
const numBlocks = blockMesh.getBlockCount();
const nbt: NBT = {
type: TagType.Compound,

View File

@ -7,6 +7,7 @@ import { saveNBT } from '../util/nbt_util';
import { Vector3 } from '../vector';
import { IExporter, TStructureExport, TStructureRegion } from './base_exporter';
import { ASSERT } from '../util/error_util';
import { OtS_BlockMesh } from '../ots_block_mesh';
export class NBTExporter extends IExporter {
public override getFormatFilter() {
@ -16,15 +17,14 @@ export class NBTExporter extends IExporter {
};
}
private _processChunk(blockMesh: BlockMesh, min: Vector3, blockNameToIndex: Map<string, number>, palette: any): Uint8Array {
private _processChunk(blockMesh: OtS_BlockMesh, min: Vector3, blockNameToIndex: Map<string, number>, palette: any): Uint8Array {
const blocks: any[] = [];
for (const block of blockMesh.getBlocks()) {
const pos = block.voxel.position;
const blockIndex = blockNameToIndex.get(block.blockInfo.name);
for (const { position, name } of blockMesh.getBlocks()) {
const blockIndex = blockNameToIndex.get(name);
if (blockIndex !== undefined) {
if (pos.x >= min.x && pos.x < min.x + 48 && pos.y >= min.y && pos.y < min.y + 48 && pos.z >= min.z && pos.z < min.z + 48) {
const translatedPos = Vector3.sub(block.voxel.position, min);
if (position.x >= min.x && position.x < min.x + 48 && position.y >= min.y && position.y < min.y + 48 && position.z >= min.z && position.z < min.z + 48) {
const translatedPos = Vector3.sub(position, min);
ASSERT(translatedPos.x >= 0 && translatedPos.x < 48);
ASSERT(translatedPos.y >= 0 && translatedPos.y < 48);
ASSERT(translatedPos.z >= 0 && translatedPos.z < 48);
@ -82,8 +82,8 @@ export class NBTExporter extends IExporter {
return saveNBT(nbt);
}
public override export(blockMesh: BlockMesh) {
const bounds = blockMesh.getVoxelMesh().getBounds();
public override export(blockMesh: OtS_BlockMesh) {
const bounds = blockMesh.getBounds();
/*
const sizeVector = bounds.getDimensions().add(1);
@ -95,7 +95,7 @@ export class NBTExporter extends IExporter {
const blockNameToIndex = new Map<string, number>();
const palette: any = [];
for (const blockName of blockMesh.getBlockPalette()) {
for (const blockName of blockMesh.calcBlocksUsed()) {
palette.push({
Name: {
type: TagType.String,

View File

@ -8,6 +8,7 @@ import { MathUtil } from '../util/math_util';
import { saveNBT } from '../util/nbt_util';
import { Vector3 } from '../vector';
import { IExporter, TStructureExport } from './base_exporter';
import { OtS_BlockMesh } from '../ots_block_mesh';
export class SchemExporter extends IExporter {
private static SCHEMA_VERSION = 2;
@ -19,8 +20,8 @@ export class SchemExporter extends IExporter {
};
}
public override export(blockMesh: BlockMesh): TStructureExport {
const bounds = blockMesh.getVoxelMesh().getBounds();
public override export(blockMesh: OtS_BlockMesh): TStructureExport {
const bounds = blockMesh.getBounds();
const sizeVector = bounds.getDimensions().add(1);
// https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-3.md#paletteObject
@ -30,7 +31,7 @@ export class SchemExporter extends IExporter {
};
let blockIndex = 1;
for (const blockName of blockMesh.getBlockPalette()) {
for (const blockName of blockMesh.calcBlocksUsed()) {
const namespacedBlockName = AppUtil.Text.namespaceBlock(blockName);
blockMapping[namespacedBlockName] = { type: TagType.Int, value: blockIndex };
@ -40,10 +41,10 @@ export class SchemExporter extends IExporter {
// 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);
for (const { position, name } of blockMesh.getBlocks()) {
const indexVector = Vector3.sub(position, bounds.min);
const bufferIndex = SchemExporter._getBufferIndex(sizeVector, indexVector);
const namespacedBlockName = AppUtil.Text.namespaceBlock(block.blockInfo.name);
const namespacedBlockName = AppUtil.Text.namespaceBlock(name);
blockData[bufferIndex] = blockMapping[namespacedBlockName].value;
}

View File

@ -6,6 +6,7 @@ import { LOG_WARN } from '../util/log_util';
import { saveNBT } from '../util/nbt_util';
import { Vector3 } from '../vector';
import { IExporter, TStructureExport } from './base_exporter';
import { OtS_BlockMesh } from '../ots_block_mesh';
export class Schematic extends IExporter {
public override getFormatFilter() {
@ -15,13 +16,13 @@ export class Schematic extends IExporter {
};
}
public override export(blockMesh: BlockMesh): TStructureExport {
public override export(blockMesh: OtS_BlockMesh): TStructureExport {
const nbt = this._convertToNBT(blockMesh);
return { type: 'single', extension: '.schematic', content: saveNBT(nbt) };
}
private _convertToNBT(blockMesh: BlockMesh): NBT {
const bounds = blockMesh.getVoxelMesh().getBounds();
private _convertToNBT(blockMesh: OtS_BlockMesh): NBT {
const bounds = blockMesh.getBounds();
const sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
const bufferSize = sizeVector.x * sizeVector.y * sizeVector.z;
@ -31,20 +32,19 @@ export class Schematic extends IExporter {
// TODO Unimplemented
const schematicBlocks: { [blockName: string]: { id: number, meta: number, name: string } } = BLOCK_IDS;
const blocks = blockMesh.getBlocks();
const unsupportedBlocks = new Set<string>();
let numBlocksUnsupported = 0;
for (const block of blocks) {
const indexVector = Vector3.sub(block.voxel.position, bounds.min);
for (const { position, name } of blockMesh.getBlocks()) {
const indexVector = Vector3.sub(position, bounds.min);
const index = this._getBufferIndex(indexVector, sizeVector);
if (block.blockInfo.name in schematicBlocks) {
const schematicBlock = schematicBlocks[block.blockInfo.name];
if (name in schematicBlocks) {
const schematicBlock = schematicBlocks[name];
blocksData[index] = new Int8Array([schematicBlock.id])[0];
metaData[index] = new Int8Array([schematicBlock.meta])[0];
} else {
blocksData[index] = 1; // Default to a Stone block
metaData[index] = 0;
unsupportedBlocks.add(block.blockInfo.name);
unsupportedBlocks.add(name);
++numBlocksUnsupported;
}
}

View File

@ -1,4 +1,4 @@
import { BlockMesh } from '../block_mesh';
import { OtS_BlockMesh } from '../ots_block_mesh';
import { IExporter, TStructureExport } from './base_exporter';
export class UncompressedJSONExporter extends IExporter {
@ -9,26 +9,24 @@ export class UncompressedJSONExporter extends IExporter {
};
}
public override export(blockMesh: BlockMesh): TStructureExport {
public override export(blockMesh: OtS_BlockMesh): TStructureExport {
const blocks = blockMesh.getBlocks();
const lines = new Array<string>();
lines.push('[');
// Serialise all block except for the last one.
for (let i = 0; i < blocks.length - 1; ++i) {
const block = blocks[i];
const pos = block.voxel.position;
lines.push(`{ "x": ${pos.x}, "y": ${pos.y}, "z": ${pos.z}, "block_name": "${block.blockInfo.name}" },`);
}
// Serialise the last block but don't include the comma at the end.
{
const block = blocks[blocks.length - 1];
const pos = block.voxel.position;
lines.push(`{ "x": ${pos.x}, "y": ${pos.y}, "z": ${pos.z}, "block_name": "${block.blockInfo.name}" }`);
}
// Serialise all block except for the last one.
for (const { name, position } of blockMesh.getBlocks()) {
lines.push(`{ "x": ${position.x}, "y": ${position.y}, "z": ${position.z}, "block_name": "${name}" },`);
}
// Update the last block to not include the comma at the end.
{
const lastIndex = lines.length - 1;
const lastEntry = lines[lastIndex];
lines[lastIndex] = lastEntry.slice(0, -1);
}
}
lines.push(']');
const json = lines.join('');

138
Core/src/ots_block_mesh.ts Normal file
View File

@ -0,0 +1,138 @@
import { FaceInfo } from "./block_atlas";
import { Bounds } from "./bounds";
import { Vector3 } from "./vector"
export type OtS_Block = {
position: Vector3,
name: string,
}
type OtS_Block_Internal = OtS_Block & {
}
export class OtS_BlockMesh {
private _blocks: Map<number, OtS_Block_Internal>;
private _isBoundsDirty: boolean;
private _bounds: Bounds;
public constructor() {
this._blocks = new Map();
this._bounds = Bounds.getEmptyBounds();
this._isBoundsDirty = false;
}
public addBlock(x: number, y: number, z: number, blockName: string, replace: boolean) {
const key = Vector3.Hash(x, y, z);
let block: (OtS_Block_Internal | undefined) = this._blocks.get(key);
if (block === undefined) {
const position = new Vector3(x, y, z);
block = {
position: position,
name: blockName,
}
this._blocks.set(key, block);
this._isBoundsDirty = true;
} else if (replace) {
block.name = blockName;
}
}
/**
* Remove a block from a given location.
*/
public removeBlock(x: number, y: number, z: number): boolean {
const key = Vector3.Hash(x, y, z);
const didRemove = this._blocks.delete(key);
this._isBoundsDirty ||= didRemove;
return didRemove;
}
/**
* Returns the colour of a voxel at a location, if one exists.
* @note Modifying the returned colour will not update the voxel's colour.
* For that, use `addVoxel` with the replaceMode set to 'replace'
*/
public getBlockAt(x: number, y: number, z: number): (OtS_Block | null) {
const key = Vector3.Hash(x, y, z);
const block = this._blocks.get(key);
if (block === undefined) {
return null;
}
return {
position: block.position.copy(),
name: block.name,
};
}
/**
* Get whether or not there is a voxel at a given location.
*/
public isBlockAt(x: number, y: number, z: number) {
const key = Vector3.Hash(x, y, z);
return this._blocks.has(key);
}
/**
* Get the bounds/dimensions of the VoxelMesh.
*/
public getBounds(): Bounds {
if (this._isBoundsDirty) {
this._bounds = Bounds.getEmptyBounds();
this._blocks.forEach((value, key) => {
this._bounds.extendByPoint(value.position);
});
this._isBoundsDirty = false;
}
return this._bounds.copy();
}
/**
* Get the number of voxels in the VoxelMesh.
*/
public getBlockCount(): number {
return this._blocks.size;
}
/**
* Iterate over the voxels in this VoxelMesh, note that these are copies
* and editing each entry will not modify the underlying voxel.
*/
public getBlocks(): IterableIterator<OtS_Block> {
const blocksCopy: OtS_Block[] = Array.from(this._blocks.values()).map((block) => {
return {
position: block.position.copy(),
name: block.name,
};
});
let currentIndex = 0;
return {
[Symbol.iterator]: function () {
return this;
},
next: () => {
if (currentIndex < blocksCopy.length) {
const block = blocksCopy[currentIndex++];
return { done: false, value: block };
} else {
return { done: true, value: undefined };
}
},
};
}
public calcBlocksUsed(): Set<string> {
const blocksUsed = new Set<string>();
for (const block of this.getBlocks()) {
blocksUsed.add(block.name);
}
return blocksUsed;
}
}