forked from mirror/ObjToSchematic
Added options to replace falling blocks
This commit is contained in:
parent
71ecb62ab0
commit
6672d2eda2
27
res/fallable_blocks.json
Normal file
27
res/fallable_blocks.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"fallable_blocks": [
|
||||
"anvil",
|
||||
"lime_concrete_powder",
|
||||
"orange_concrete_powder",
|
||||
"black_concrete_powder",
|
||||
"brown_concrete_powder",
|
||||
"cyan_concrete_powder",
|
||||
"light_gray_concrete_powder",
|
||||
"purple_concrete_powder",
|
||||
"magenta_concrete_powder",
|
||||
"light_blue_concrete_powder",
|
||||
"yellow_concrete_powder",
|
||||
"white_concrete_powder",
|
||||
"blue_concrete_powder",
|
||||
"red_concrete_powder",
|
||||
"gray_concrete_powder",
|
||||
"pink_concrete_powder",
|
||||
"green_concrete_powder",
|
||||
"dragon_egg",
|
||||
"gravel",
|
||||
"pointed_dripstone",
|
||||
"red_sand",
|
||||
"sand",
|
||||
"scaffolding"
|
||||
]
|
||||
}
|
@ -8,7 +8,7 @@ import { ASSERT, ColourSpace, AppError, LOG, LOG_ERROR, LOG_WARN, TIME_START, TI
|
||||
|
||||
import { remote } from 'electron';
|
||||
import { VoxelMesh, VoxelMeshParams } from './voxel_mesh';
|
||||
import { BlockMesh, BlockMeshParams } from './block_mesh';
|
||||
import { BlockMesh, BlockMeshParams, FallableBehaviour } from './block_mesh';
|
||||
import { TextureFiltering } from './texture';
|
||||
import { RayVoxeliser } from './voxelisers/ray-voxeliser';
|
||||
import { IVoxeliser } from './voxelisers/base-voxeliser';
|
||||
@ -193,6 +193,7 @@ export class AppContext {
|
||||
blockPalette: uiElements.blockPalette.getCachedValue(),
|
||||
ditheringEnabled: uiElements.dithering.getCachedValue() === 'on',
|
||||
colourSpace: uiElements.colourSpace.getCachedValue() === 'rgb' ? ColourSpace.RGB : ColourSpace.LAB,
|
||||
fallable: uiElements.fallable.getCachedValue() as FallableBehaviour,
|
||||
};
|
||||
|
||||
this._loadedBlockMesh = BlockMesh.createFromVoxelMesh(this._loadedVoxelMesh, blockMeshParams);
|
||||
|
@ -3,12 +3,12 @@ import { ASSERT, ColourSpace, RGB } from './util';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
interface IBlockAssigner {
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace): BlockInfo;
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace, exclude?: string[]): BlockInfo;
|
||||
}
|
||||
|
||||
export class BasicBlockAssigner implements IBlockAssigner {
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace): BlockInfo {
|
||||
return BlockAtlas.Get.getBlock(voxelColour, colourSpace);
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace, exclude?: string[]): BlockInfo {
|
||||
return BlockAtlas.Get.getBlock(voxelColour, colourSpace, exclude);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ export class OrderedDitheringBlockAssigner implements IBlockAssigner {
|
||||
return (OrderedDitheringBlockAssigner._mapMatrix[index] / (size * size * size)) - 0.5;
|
||||
}
|
||||
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace): BlockInfo {
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace, exclude?: string[]): BlockInfo {
|
||||
const size = OrderedDitheringBlockAssigner._size;
|
||||
const map = this._getThresholdValue(
|
||||
Math.abs(voxelPosition.x % size),
|
||||
@ -50,6 +50,6 @@ export class OrderedDitheringBlockAssigner implements IBlockAssigner {
|
||||
((255 * voxelColour.b) + map * OrderedDitheringBlockAssigner._threshold) / 255,
|
||||
);
|
||||
|
||||
return BlockAtlas.Get.getBlock(newVoxelColour, colourSpace);
|
||||
return BlockAtlas.Get.getBlock(newVoxelColour, colourSpace, exclude);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { HashMap } from './hash_map';
|
||||
import { UV, RGB, ASSERT, fileExists, ColourSpace, ATLASES_DIR, PALETTES_DIR, AppError, LOG_WARN } from './util';
|
||||
import { UV, RGB, ASSERT, fileExists, ColourSpace, ATLASES_DIR, PALETTES_DIR, AppError, LOG_WARN, LOG } from './util';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
import fs from 'fs';
|
||||
@ -124,12 +124,12 @@ export class BlockAtlas {
|
||||
this._paletteLoaded = true;
|
||||
}
|
||||
|
||||
public getBlock(voxelColour: RGB, colourSpace: ColourSpace): BlockInfo {
|
||||
public getBlock(voxelColour: RGB, colourSpace: ColourSpace, exclude?: string[]): BlockInfo {
|
||||
ASSERT(this._atlasLoaded, 'No atlas has been loaded');
|
||||
ASSERT(this._paletteLoaded, 'No palette has been loaded');
|
||||
|
||||
const cachedBlockIndex = this._cachedBlocks.get(voxelColour.toVector3());
|
||||
if (cachedBlockIndex) {
|
||||
if (cachedBlockIndex && exclude === undefined) {
|
||||
return this._atlasBlocks[cachedBlockIndex];
|
||||
}
|
||||
|
||||
@ -137,6 +137,10 @@ export class BlockAtlas {
|
||||
let blockChoiceIndex!: number;
|
||||
|
||||
for (const paletteBlockName of this._palette) {
|
||||
if (exclude?.includes(paletteBlockName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Optimise Use hash map for blockIndex instead of linear search
|
||||
const blockIndex: (number | undefined) = this._paletteBlockToBlockInfoIndex.get(paletteBlockName);
|
||||
ASSERT(blockIndex !== undefined);
|
||||
@ -155,7 +159,9 @@ export class BlockAtlas {
|
||||
throw new AppError('The chosen palette does not have suitable blocks');
|
||||
}
|
||||
|
||||
this._cachedBlocks.add(voxelColour.toVector3(), blockChoiceIndex);
|
||||
if (exclude === undefined) {
|
||||
this._cachedBlocks.add(voxelColour.toVector3(), blockChoiceIndex);
|
||||
}
|
||||
return this._atlasBlocks[blockChoiceIndex];
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,35 @@
|
||||
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from './block_assigner';
|
||||
import { Voxel, VoxelMesh } from './voxel_mesh';
|
||||
import { BlockAtlas, BlockInfo } from './block_atlas';
|
||||
import { ColourSpace, AppError, ASSERT } from './util';
|
||||
import { ColourSpace, AppError, ASSERT, RESOURCES_DIR, LOG } from './util';
|
||||
import { Renderer } from './renderer';
|
||||
import { AppConstants } from './constants';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { StatusHandler } from './status';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
interface Block {
|
||||
voxel: Voxel;
|
||||
blockInfo: BlockInfo;
|
||||
}
|
||||
|
||||
export type FallableBehaviour = 'replace-falling' | 'replace-fallable' | 'place-string' | 'do-nothing';
|
||||
|
||||
export interface BlockMeshParams {
|
||||
textureAtlas: string,
|
||||
blockPalette: string,
|
||||
ditheringEnabled: boolean,
|
||||
colourSpace: ColourSpace,
|
||||
fallable: FallableBehaviour,
|
||||
}
|
||||
|
||||
export class BlockMesh {
|
||||
private _blockPalette: string[];
|
||||
private _blocks: Block[];
|
||||
private _voxelMesh: VoxelMesh;
|
||||
private _fallableBlocks: string[];
|
||||
|
||||
public static createFromVoxelMesh(voxelMesh: VoxelMesh, blockMeshParams: BlockMeshParams) {
|
||||
const blockMesh = new BlockMesh(voxelMesh);
|
||||
@ -32,6 +41,9 @@ export class BlockMesh {
|
||||
this._blockPalette = [];
|
||||
this._blocks = [];
|
||||
this._voxelMesh = voxelMesh;
|
||||
|
||||
const fallableBlocksString = fs.readFileSync(path.join(RESOURCES_DIR, 'fallable_blocks.json'), 'utf-8');
|
||||
this._fallableBlocks = JSON.parse(fallableBlocksString).fallable_blocks;
|
||||
}
|
||||
|
||||
private _assignBlocks(blockMeshParams: BlockMeshParams) {
|
||||
@ -40,10 +52,27 @@ export class BlockMesh {
|
||||
|
||||
const blockAssigner = blockMeshParams.ditheringEnabled ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner();
|
||||
|
||||
let countFalling = 0;
|
||||
const voxels = this._voxelMesh.getVoxels();
|
||||
for (let voxelIndex = 0; voxelIndex < voxels.length; ++voxelIndex) {
|
||||
const voxel = voxels[voxelIndex];
|
||||
const block = blockAssigner.assignBlock(voxel.colour, voxel.position, blockMeshParams.colourSpace);
|
||||
let block = blockAssigner.assignBlock(voxel.colour, voxel.position, blockMeshParams.colourSpace);
|
||||
|
||||
const isFallable = this._fallableBlocks.includes(block.name);
|
||||
const isSupported = this._voxelMesh.isVoxelAt(Vector3.add(voxel.position, new Vector3(0, -1, 0)));
|
||||
|
||||
if (isFallable && !isSupported) {
|
||||
++countFalling;
|
||||
}
|
||||
|
||||
let shouldReplace = (blockMeshParams.fallable === 'replace-fallable' && isFallable);
|
||||
shouldReplace ||= (blockMeshParams.fallable === 'replace-falling' && isFallable && !isSupported);
|
||||
|
||||
if (shouldReplace) {
|
||||
const replacedBlock = blockAssigner.assignBlock(voxel.colour, voxel.position, blockMeshParams.colourSpace, this._fallableBlocks);
|
||||
// LOG(`Replacing ${block.name} with ${replacedBlock.name}`);
|
||||
block = replacedBlock;
|
||||
}
|
||||
|
||||
this._blocks.push({
|
||||
voxel: voxel,
|
||||
@ -53,6 +82,10 @@ export class BlockMesh {
|
||||
this._blockPalette.push(block.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (blockMeshParams.fallable === 'do-nothing' && countFalling > 0) {
|
||||
StatusHandler.Get.add('warning', `${countFalling.toLocaleString()} blocks will fall under gravity when this structure is placed`);
|
||||
}
|
||||
}
|
||||
|
||||
public getBlocks(): Block[] {
|
||||
|
@ -4,6 +4,7 @@ import { ASSERT } from '../../util';
|
||||
export interface ComboBoxItem {
|
||||
id: string;
|
||||
displayText: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export class ComboBoxElement extends LabelledElement<string> {
|
||||
@ -17,7 +18,7 @@ export class ComboBoxElement extends LabelledElement<string> {
|
||||
public generateInnerHTML() {
|
||||
let itemsHTML = '';
|
||||
for (const item of this._items) {
|
||||
itemsHTML += `<option value="${item.id}">${item.displayText}</option>`;
|
||||
itemsHTML += `<option value="${item.id}" title="${item.tooltip || ''}">${item.displayText}</option>`;
|
||||
}
|
||||
|
||||
return `
|
||||
|
@ -3,16 +3,21 @@ import { ASSERT, getRandomID } from '../../util';
|
||||
export class LabelElement {
|
||||
private _id: string;
|
||||
private _text: string;
|
||||
private _description?: string;
|
||||
|
||||
constructor(text: string) {
|
||||
constructor(text: string, description?: string) {
|
||||
this._id = getRandomID();
|
||||
this._text = text;
|
||||
this._description = description;
|
||||
}
|
||||
|
||||
public generateHTML(): string {
|
||||
const description = this._description ? `<br><div style="font-weight: 300; font-size: 85%; color: var(--text-disabled);">
|
||||
${this._description}
|
||||
</div>` : '';
|
||||
return `
|
||||
<div class="prop-left" id="${this._id}">
|
||||
${this._text}
|
||||
${this._text}${description}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ export abstract class LabelledElement<Type> extends BaseUIElement<Type> {
|
||||
|
||||
public constructor(label: string) {
|
||||
super(label);
|
||||
this._label = label;
|
||||
this._labelElement = new LabelElement(label);
|
||||
}
|
||||
|
||||
@ -24,4 +25,8 @@ export abstract class LabelledElement<Type> extends BaseUIElement<Type> {
|
||||
protected _onEnabledChanged() {
|
||||
this._labelElement.setEnabled(this._isEnabled);
|
||||
}
|
||||
|
||||
public addDescription(text: string) {
|
||||
this._labelElement = new LabelElement(this._label, text);
|
||||
}
|
||||
}
|
||||
|
@ -94,8 +94,32 @@ export class UI {
|
||||
{ id: 'rgb', displayText: 'RGB (faster)' },
|
||||
{ id: 'lab', displayText: 'LAB (recommended, slow)' },
|
||||
]),
|
||||
'fallable': new ComboBoxElement('Fallable blocks', [
|
||||
{
|
||||
id: 'replace-falling',
|
||||
displayText: 'Replace falling with solid',
|
||||
tooltip: 'Replace all blocks that can fall with solid blocks',
|
||||
},
|
||||
{
|
||||
id: 'replace-fallable',
|
||||
displayText: 'Replace fallable with solid',
|
||||
tooltip: 'Replace all blocks that will fall with solid blocks',
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'place-string',
|
||||
displayText: 'Place string under',
|
||||
tooltip: 'Place string blocks under all blocks that would fall otherwise',
|
||||
},
|
||||
*/
|
||||
{
|
||||
id: 'do-nothing',
|
||||
displayText: 'Do nothing',
|
||||
tooltip: 'Let the block fall',
|
||||
},
|
||||
]),
|
||||
},
|
||||
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'colourSpace'],
|
||||
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'colourSpace', 'fallable'],
|
||||
submitButton: new ButtonElement('Assign blocks', () => {
|
||||
this._appContext.do(EAction.Assign);
|
||||
}),
|
||||
@ -242,6 +266,9 @@ export class UI {
|
||||
|
||||
constructor(appContext: AppContext) {
|
||||
this._appContext = appContext;
|
||||
|
||||
this._ui.assign.elements.textureAtlas.addDescription('Textures to use and colour-match with');
|
||||
this._ui.assign.elements.fallable.addDescription('Read tooltips for more info');
|
||||
}
|
||||
|
||||
public build() {
|
||||
|
@ -141,6 +141,7 @@ select {
|
||||
color: var(--text-standard);
|
||||
background: var(--prop-standard);
|
||||
border: 1px solid rgb(255, 255, 255, 0.0);
|
||||
max-width: 100%;
|
||||
}
|
||||
select:hover:enabled {
|
||||
color: #C6C6C6;
|
||||
|
@ -16,6 +16,7 @@ export const headlessConfig = {
|
||||
blockPalette: 'all-supported', // Must be a palette name that exists in /resources/palettes
|
||||
ditheringEnabled: true,
|
||||
colourSpace: 'rgb', // 'rgb' / 'lab';
|
||||
fallable: 'replace-falling', // 'replace-fallable' / 'place-string';
|
||||
},
|
||||
},
|
||||
export: {
|
||||
|
@ -2,7 +2,7 @@ import { Mesh } from '../src/mesh';
|
||||
import { ObjImporter } from '../src/importers/obj_importer';
|
||||
import { IVoxeliser } from '../src/voxelisers/base-voxeliser';
|
||||
import { VoxelMesh, VoxelMeshParams } from '../src/voxel_mesh';
|
||||
import { BlockMesh, BlockMeshParams } from '../src/block_mesh';
|
||||
import { BlockMesh, BlockMeshParams, FallableBehaviour } from '../src/block_mesh';
|
||||
import { IExporter} from '../src/exporters/base_exporter';
|
||||
import { Schematic } from '../src/exporters/schematic_exporter';
|
||||
import { Litematic } from '../src/exporters/litematic_exporter';
|
||||
@ -32,6 +32,7 @@ void async function main() {
|
||||
blockPalette: headlessConfig.palette.blockMeshParams.blockPalette,
|
||||
ditheringEnabled: headlessConfig.palette.blockMeshParams.ditheringEnabled,
|
||||
colourSpace: headlessConfig.palette.blockMeshParams.colourSpace === 'rgb' ? ColourSpace.RGB : ColourSpace.LAB,
|
||||
fallable: headlessConfig.palette.blockMeshParams.fallable as FallableBehaviour,
|
||||
},
|
||||
});
|
||||
_export(blockMesh, {
|
||||
|
Loading…
Reference in New Issue
Block a user