mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2025-12-11 20:15:30 +01:00
Added OtS_BlockMesh and updated exporters to use
This commit is contained in:
parent
c51c4d900c
commit
2754ecf3f6
@ -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;
|
||||
}
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
138
Core/src/ots_block_mesh.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user