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

View File

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

View File

@ -1,12 +1,15 @@
import { BaseUIElement } from './elements/base'; import { BaseUIElement } from './elements/base';
import { SliderElement } from './elements/slider'; import { SliderElement } from './elements/slider';
import { ComboBoxElement } from './elements/combobox'; import { ComboBoxElement, ComboBoxItem } from './elements/combobox';
import { FileInputElement } from './elements/file_input'; import { FileInputElement } from './elements/file_input';
import { ButtonElement } from './elements/button'; import { ButtonElement } from './elements/button';
import { OutputElement } from './elements/output'; import { OutputElement } from './elements/output';
import { Action, AppContext } from '../app_context'; import { Action, AppContext } from '../app_context';
import { LOG } from '../util'; import { LOG } from '../util';
import fs from 'fs';
import path from 'path';
export interface Group { export interface Group {
label: string; label: string;
elements: { [key: string]: BaseUIElement }; elements: { [key: string]: BaseUIElement };
@ -66,18 +69,14 @@ export class UI {
'palette': { 'palette': {
label: 'Palette', label: 'Palette',
elements: { elements: {
'blockPalette': new ComboBoxElement('Block palette', [ 'textureAtlas': new ComboBoxElement('Texture atlas', this._getTextureAtlases()),
{ id: 'default', displayText: 'Default' }, 'blockPalette': new ComboBoxElement('Block palette', this._getBlockPalettes()),
]),
'choiceMethod': new ComboBoxElement('Choice method', [
{ id: 'euclidian', displayText: 'Euclidian distance' },
]),
'dithering': new ComboBoxElement('Dithering', [ 'dithering': new ComboBoxElement('Dithering', [
{ id: 'on', displayText: 'On (recommended)' }, { id: 'on', displayText: 'On (recommended)' },
{ id: 'off', displayText: 'Off' }, { id: 'off', displayText: 'Off' },
]), ]),
}, },
elementsOrder: ['blockPalette', 'choiceMethod', 'dithering'], elementsOrder: ['textureAtlas', 'blockPalette', 'dithering'],
submitButton: new ButtonElement('Assign blocks', () => { submitButton: new ButtonElement('Assign blocks', () => {
AppContext.Get.do(Action.Palette); AppContext.Get.do(Action.Palette);
}), }),
@ -228,4 +227,24 @@ export class UI {
const key = this.uiOrder[action]; const key = this.uiOrder[action];
return this._uiDull[key]; 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;
}
} }