Added support for custom block palettes

This commit is contained in:
Lucas Dower 2022-02-19 22:30:47 +00:00
parent ed2a61af1a
commit 1b9e07d899
3 changed files with 66 additions and 21 deletions

View File

@ -1,5 +1,5 @@
import { HashMap } from './hash_map';
import { UV, RGB, ASSERT } from './util';
import { UV, RGB, ASSERT, fileExists } from './util';
import { Vector3 } from './vector';
import fs from 'fs';
@ -33,12 +33,19 @@ export enum Block {
Dirt = 3.0,
Cobblestone = 4.0
}
interface BlockPalette {
blocks: string[];
}
/* eslint-enable */
export class BlockAtlas {
private _cachedBlocks: HashMap<Vector3, number>;
private _blocks: Array<BlockInfo>;
private _palette: string[];
private _atlasSize: number;
private _atlasLoaded: boolean;
private _paletteLoaded: boolean;
private static _instance: BlockAtlas;
public static get Get() {
@ -50,6 +57,8 @@ export class BlockAtlas {
this._blocks = [];
this._atlasSize = 0;
this._atlasLoaded = false;
this._palette = [];
this._paletteLoaded = false;
this.loadAtlas(path.join(__dirname, '../resources/atlases/vanilla.atlas'));
}
@ -76,8 +85,21 @@ export class BlockAtlas {
this._atlasLoaded = true;
}
public loadPalette(paletteID: string) {
this._cachedBlocks = new HashMap(1024);
const paletteDir = path.join(__dirname, '../resources/palettes', paletteID + '.palette');
ASSERT(fileExists(paletteDir), `Palette to load does not exist ${paletteDir}`);
const palette: BlockPalette = JSON.parse(fs.readFileSync(paletteDir, 'utf8'));
this._palette = palette.blocks;
this._paletteLoaded = true;
}
public getBlock(voxelColour: RGB): BlockInfo {
ASSERT(this._atlasLoaded);
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) {
@ -89,12 +111,14 @@ export class BlockAtlas {
for (let i = 0; i < this._blocks.length; ++i) {
const block: BlockInfo = this._blocks[i];
const blockAvgColour = block.colour as RGB;
const distance = RGB.distance(blockAvgColour, voxelColour);
if (this._palette.includes(block.name)) {
const blockAvgColour = block.colour as RGB;
const distance = RGB.distance(blockAvgColour, voxelColour);
if (distance < minDistance) {
minDistance = distance;
blockChoiceIndex = i;
if (distance < minDistance) {
minDistance = distance;
blockChoiceIndex = i;
}
}
}

View File

@ -1,6 +1,6 @@
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from './block_assigner';
import { Voxel, VoxelMesh } from './voxel_mesh';
import { BlockInfo } from './block_atlas';
import { BlockAtlas, BlockInfo } from './block_atlas';
import { CustomError, LOG } from './util';
import { Renderer } from './renderer';
import { UI } from './ui/layout';
@ -11,7 +11,6 @@ interface Block {
}
export class BlockMesh {
private _ditheringEnabled: boolean;
private _blockPalette: string[];
private _blocks: Block[];
private _voxelMesh?: VoxelMesh;
@ -19,17 +18,20 @@ export class BlockMesh {
public constructor() {
LOG('New block mesh');
this._ditheringEnabled = UI.Get.layout.palette.elements.dithering.getCachedValue() as string === 'on';
this._blockPalette = [];
this._blocks = [];
}
public assignBlocks(voxelMesh: VoxelMesh) {
LOG('Assigning blocks');
const blockPalette = UI.Get.layout.palette.elements.blockPalette.getCachedValue() as string;
BlockAtlas.Get.loadPalette(blockPalette);
const ditheringEnabled = UI.Get.layout.palette.elements.dithering.getCachedValue() as string === 'on';
const blockAssigner = ditheringEnabled ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner();
const voxels = voxelMesh.getVoxels();
const blockAssigner = this._ditheringEnabled ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner();
for (let voxelIndex = 0; voxelIndex < voxels.length; ++voxelIndex) {
const voxel = voxels[voxelIndex];
const block = blockAssigner.assignBlock(voxel.colour, voxel.position);

View File

@ -1,12 +1,15 @@
import { BaseUIElement } from './elements/base';
import { SliderElement } from './elements/slider';
import { ComboBoxElement } from './elements/combobox';
import { ComboBoxElement, ComboBoxItem } from './elements/combobox';
import { FileInputElement } from './elements/file_input';
import { ButtonElement } from './elements/button';
import { OutputElement } from './elements/output';
import { Action, AppContext } from '../app_context';
import { LOG } from '../util';
import fs from 'fs';
import path from 'path';
export interface Group {
label: string;
elements: { [key: string]: BaseUIElement };
@ -66,18 +69,14 @@ export class UI {
'palette': {
label: 'Palette',
elements: {
'blockPalette': new ComboBoxElement('Block palette', [
{ id: 'default', displayText: 'Default' },
]),
'choiceMethod': new ComboBoxElement('Choice method', [
{ id: 'euclidian', displayText: 'Euclidian distance' },
]),
'textureAtlas': new ComboBoxElement('Texture atlas', this._getTextureAtlases()),
'blockPalette': new ComboBoxElement('Block palette', this._getBlockPalettes()),
'dithering': new ComboBoxElement('Dithering', [
{ id: 'on', displayText: 'On (recommended)' },
{ id: 'off', displayText: 'Off' },
]),
},
elementsOrder: ['blockPalette', 'choiceMethod', 'dithering'],
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering'],
submitButton: new ButtonElement('Assign blocks', () => {
AppContext.Get.do(Action.Palette);
}),
@ -228,4 +227,24 @@ export class UI {
const key = this.uiOrder[action];
return this._uiDull[key];
}
private _getTextureAtlases(): ComboBoxItem[] {
return [{ id: 'vanilla', displayText: 'Vanilla' }];
}
private _getBlockPalettes(): ComboBoxItem[] {
const blockPalettes: ComboBoxItem[] = [];
const palettesDir = path.join(__dirname, '../../resources/palettes');
fs.readdirSync(palettesDir).forEach((file) => {
if (file.endsWith('.palette')) {
const paletteID = file.split('.')[0];
let paletteName = paletteID.replace('-', ' ').toLowerCase();
paletteName = paletteName.charAt(0).toUpperCase() + paletteName.slice(1);
blockPalettes.push({ id: paletteID, displayText: paletteName });
}
});
return blockPalettes;
}
}