diff --git a/Core/src/exporters/base_exporter.ts b/Core/src/exporters/base_exporter.ts index be47092..ee94daa 100644 --- a/Core/src/exporters/base_exporter.ts +++ b/Core/src/exporters/base_exporter.ts @@ -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; } diff --git a/Core/src/exporters/indexed_json_exporter .ts b/Core/src/exporters/indexed_json_exporter .ts index ff06746..f0321aa 100644 --- a/Core/src/exporters/indexed_json_exporter .ts +++ b/Core/src/exporters/indexed_json_exporter .ts @@ -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(); const indexToBlock = new Map(); for (let i = 0; i < blocksUsed.length; ++i) { @@ -23,10 +24,8 @@ export class IndexedJSONExporter extends IExporter { const blockArray = new Array>(); // 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({ diff --git a/Core/src/exporters/litematic_exporter.ts b/Core/src/exporters/litematic_exporter.ts index 1991a32..96e347f 100644 --- a/Core/src/exporters/litematic_exporter.ts +++ b/Core/src/exporters/litematic_exporter.ts @@ -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, diff --git a/Core/src/exporters/nbt_exporter.ts b/Core/src/exporters/nbt_exporter.ts index e27bbae..ab24ab5 100644 --- a/Core/src/exporters/nbt_exporter.ts +++ b/Core/src/exporters/nbt_exporter.ts @@ -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, palette: any): Uint8Array { + private _processChunk(blockMesh: OtS_BlockMesh, min: Vector3, blockNameToIndex: Map, 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(); const palette: any = []; - for (const blockName of blockMesh.getBlockPalette()) { + for (const blockName of blockMesh.calcBlocksUsed()) { palette.push({ Name: { type: TagType.String, diff --git a/Core/src/exporters/schem_exporter.ts b/Core/src/exporters/schem_exporter.ts index 4196dd8..5c268a0 100644 --- a/Core/src/exporters/schem_exporter.ts +++ b/Core/src/exporters/schem_exporter.ts @@ -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(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; } diff --git a/Core/src/exporters/schematic_exporter.ts b/Core/src/exporters/schematic_exporter.ts index 8408284..5b792d8 100644 --- a/Core/src/exporters/schematic_exporter.ts +++ b/Core/src/exporters/schematic_exporter.ts @@ -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(); 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; } } diff --git a/Core/src/exporters/uncompressed_json_exporter.ts b/Core/src/exporters/uncompressed_json_exporter.ts index 1d20c40..5310b91 100644 --- a/Core/src/exporters/uncompressed_json_exporter.ts +++ b/Core/src/exporters/uncompressed_json_exporter.ts @@ -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(); 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(''); diff --git a/Core/src/ots_block_mesh.ts b/Core/src/ots_block_mesh.ts new file mode 100644 index 0000000..de28ab2 --- /dev/null +++ b/Core/src/ots_block_mesh.ts @@ -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; + 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 { + 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 { + const blocksUsed = new Set(); + + for (const block of this.getBlocks()) { + blocksUsed.add(block.name); + } + + return blocksUsed; + } +} \ No newline at end of file