mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2025-02-23 13:49:07 +08:00
Merge pull request #76 from LucasDower/0.6-colour-accuracy
0.6 colour accuracy
This commit is contained in:
commit
ceee1b0a7d
@ -10,7 +10,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint --fix src tools tests --ext .ts",
|
||||
"build": "tsc",
|
||||
"test": "jest --config jestconfig.json --runInBand",
|
||||
"test": "jest --config jestconfig.json",
|
||||
"start": "npm run build && electron ./dist/src/main.js --enable-logging --remote-debugging-port=9222",
|
||||
"atlas": "node ./dist/tools/build-atlas.js",
|
||||
"palette": "node ./dist/tools/build-palette.js",
|
||||
|
@ -1,27 +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"
|
||||
"minecraft:anvil",
|
||||
"minecraft:lime_concrete_powder",
|
||||
"minecraft:orange_concrete_powder",
|
||||
"minecraft:black_concrete_powder",
|
||||
"minecraft:brown_concrete_powder",
|
||||
"minecraft:cyan_concrete_powder",
|
||||
"minecraft:light_gray_concrete_powder",
|
||||
"minecraft:purple_concrete_powder",
|
||||
"minecraft:magenta_concrete_powder",
|
||||
"minecraft:light_blue_concrete_powder",
|
||||
"minecraft:yellow_concrete_powder",
|
||||
"minecraft:white_concrete_powder",
|
||||
"minecraft:blue_concrete_powder",
|
||||
"minecraft:red_concrete_powder",
|
||||
"minecraft:gray_concrete_powder",
|
||||
"minecraft:pink_concrete_powder",
|
||||
"minecraft:green_concrete_powder",
|
||||
"minecraft:dragon_egg",
|
||||
"minecraft:gravel",
|
||||
"minecraft:pointed_dripstone",
|
||||
"minecraft:red_sand",
|
||||
"minecraft:sand",
|
||||
"minecraft:scaffolding"
|
||||
]
|
||||
}
|
@ -331,6 +331,7 @@ export class AppContext {
|
||||
blockAssigner: uiElements.dithering.getCachedValue(),
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: uiElements.fallable.getCachedValue() as FallableBehaviour,
|
||||
resolution: Math.pow(2, uiElements.colourAccuracy.getCachedValue()),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { AtlasPalette } from '../block_assigner';
|
||||
import { AtlasPalette, TBlockCollection } from '../block_assigner';
|
||||
import { BlockInfo } from '../block_atlas';
|
||||
import { RGBA } from '../colour';
|
||||
import { RGBA, RGBAUtil } from '../colour';
|
||||
import { ColourSpace } from '../util';
|
||||
import { Vector3 } from '../vector';
|
||||
|
||||
export interface IBlockAssigner {
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, colourSpace: ColourSpace, exclude?: string[]): BlockInfo;
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, resolution: RGBAUtil.TColourAccuracy, colourSpace: ColourSpace, blockCollection: TBlockCollection): BlockInfo;
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { AtlasPalette } from '../block_assigner';
|
||||
import { AtlasPalette, TBlockCollection } from '../block_assigner';
|
||||
import { BlockInfo } from '../block_atlas';
|
||||
import { RGBA } from '../colour';
|
||||
import { RGBA, RGBAUtil } from '../colour';
|
||||
import { ColourSpace } from '../util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IBlockAssigner } from './base_assigner';
|
||||
|
||||
export class BasicBlockAssigner implements IBlockAssigner {
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, colourSpace: ColourSpace, exclude?: string[]): BlockInfo {
|
||||
return atlasPalette.getBlock(voxelColour, exclude);
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, resolution: RGBAUtil.TColourAccuracy, colourSpace: ColourSpace, blockCollection: TBlockCollection): BlockInfo {
|
||||
return atlasPalette.getBlock(voxelColour, blockCollection, resolution);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { AtlasPalette } from '../block_assigner';
|
||||
import { AtlasPalette, TBlockCollection } from '../block_assigner';
|
||||
import { BlockInfo } from '../block_atlas';
|
||||
import { RGBA } from '../colour';
|
||||
import { RGBA, RGBAUtil } from '../colour';
|
||||
import { ColourSpace } from '../util';
|
||||
import { ASSERT } from '../util/error_util';
|
||||
import { Vector3 } from '../vector';
|
||||
@ -30,7 +30,7 @@ export class OrderedDitheringBlockAssigner implements IBlockAssigner {
|
||||
return (OrderedDitheringBlockAssigner._mapMatrix[index] / (size * size * size)) - 0.5;
|
||||
}
|
||||
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, colourSpace: ColourSpace, exclude?: string[]): BlockInfo {
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, resolution: RGBAUtil.TColourAccuracy, colourSpace: ColourSpace, blockCollection: TBlockCollection): BlockInfo {
|
||||
const size = OrderedDitheringBlockAssigner._size;
|
||||
const map = this._getThresholdValue(
|
||||
Math.abs(voxelPosition.x % size),
|
||||
@ -45,6 +45,6 @@ export class OrderedDitheringBlockAssigner implements IBlockAssigner {
|
||||
a: ((255 * voxelColour.a) + map * OrderedDitheringBlockAssigner._threshold) / 255,
|
||||
};
|
||||
|
||||
return atlasPalette.getBlock(newVoxelColour, exclude);
|
||||
return atlasPalette.getBlock(newVoxelColour, blockCollection, resolution);
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,23 @@
|
||||
import { AtlasPalette } from '../block_assigner';
|
||||
import { AtlasPalette, TBlockCollection } from '../block_assigner';
|
||||
import { BlockInfo } from '../block_atlas';
|
||||
import { RGBA } from '../colour';
|
||||
import { RGBA, RGBAUtil } from '../colour';
|
||||
import { ColourSpace } from '../util';
|
||||
import { ASSERT } from '../util/error_util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IBlockAssigner } from './base_assigner';
|
||||
|
||||
export class RandomDitheringBlockAssigner implements IBlockAssigner {
|
||||
/** 4x4x4 */
|
||||
private static _size = 4;
|
||||
private static _threshold = 256 / 8;
|
||||
private static _deviation = 32;
|
||||
|
||||
private _mapMatrix = [
|
||||
0, 16, 2, 18, 48, 32, 50, 34,
|
||||
6, 22, 4, 20, 54, 38, 52, 36,
|
||||
24, 40, 26, 42, 8, 56, 10, 58,
|
||||
30, 46, 28, 44, 14, 62, 12, 60,
|
||||
3, 19, 5, 21, 51, 35, 53, 37,
|
||||
1, 17, 7, 23, 49, 33, 55, 39,
|
||||
27, 43, 29, 45, 11, 59, 13, 61,
|
||||
25, 41, 31, 47, 9, 57, 15, 63,
|
||||
];
|
||||
|
||||
private _getThresholdValue(x: number, y: number, z: number) {
|
||||
const size = RandomDitheringBlockAssigner._size;
|
||||
ASSERT(0 <= x && x < size && 0 <= y && y < size && 0 <= z && z < size);
|
||||
const index = (x + (size * y) + (size * size * z));
|
||||
ASSERT(0 <= index && index < size * size * size);
|
||||
return (this._mapMatrix[index] / (size * size * size)) - 0.5;
|
||||
}
|
||||
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, colourSpace: ColourSpace, exclude?: string[]): BlockInfo {
|
||||
this._mapMatrix = this._mapMatrix
|
||||
.map((value) => ({ value, sort: Math.random() }))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.map(({ value }) => value);
|
||||
|
||||
const size = RandomDitheringBlockAssigner._size;
|
||||
const map = this._getThresholdValue(
|
||||
Math.abs(voxelPosition.x % size),
|
||||
Math.abs(voxelPosition.y % size),
|
||||
Math.abs(voxelPosition.z % size),
|
||||
);
|
||||
assignBlock(atlasPalette: AtlasPalette, voxelColour: RGBA, voxelPosition: Vector3, resolution: RGBAUtil.TColourAccuracy, colourSpace: ColourSpace, blockCollection: TBlockCollection): BlockInfo {
|
||||
const map = Math.random() - 0.5;
|
||||
|
||||
const newVoxelColour: RGBA = {
|
||||
r: ((255 * voxelColour.r) + map * RandomDitheringBlockAssigner._threshold) / 255,
|
||||
g: ((255 * voxelColour.g) + map * RandomDitheringBlockAssigner._threshold) / 255,
|
||||
b: ((255 * voxelColour.b) + map * RandomDitheringBlockAssigner._threshold) / 255,
|
||||
a: ((255 * voxelColour.a) + map * RandomDitheringBlockAssigner._threshold) / 255,
|
||||
r: ((255 * voxelColour.r) + map * RandomDitheringBlockAssigner._deviation) / 255,
|
||||
g: ((255 * voxelColour.g) + map * RandomDitheringBlockAssigner._deviation) / 255,
|
||||
b: ((255 * voxelColour.b) + map * RandomDitheringBlockAssigner._deviation) / 255,
|
||||
a: ((255 * voxelColour.a) + map * RandomDitheringBlockAssigner._deviation) / 255,
|
||||
};
|
||||
|
||||
return atlasPalette.getBlock(newVoxelColour, exclude);
|
||||
return atlasPalette.getBlock(newVoxelColour, blockCollection, resolution);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { Atlas } from './atlas';
|
||||
import { RGBA } from './colour';
|
||||
import { Atlas, TAtlasBlock } from './atlas';
|
||||
import { RGBA, RGBAUtil } from './colour';
|
||||
import { Palette } from './palette';
|
||||
import { AppTypes } from './util';
|
||||
import { AppTypes, TOptional } from './util';
|
||||
import { ASSERT } from './util/error_util';
|
||||
|
||||
export type TBlockCollection = {
|
||||
blocks: Map<AppTypes.TNamespacedBlockName, TAtlasBlock>,
|
||||
cache: Map<number, TAtlasBlock>,
|
||||
}
|
||||
|
||||
/**
|
||||
* A new instance of AtlasPalette is created each time
|
||||
* a new voxel mesh is voxelised.
|
||||
*/
|
||||
export class AtlasPalette {
|
||||
private _atlas: Atlas;
|
||||
private _palette: Palette;
|
||||
@ -14,7 +24,71 @@ export class AtlasPalette {
|
||||
this._palette.removeMissingAtlasBlocks(this._atlas);
|
||||
}
|
||||
|
||||
public getBlock(colour: RGBA, exclude?: AppTypes.TNamespacedBlockName[]) {
|
||||
return this._palette.getBlock(colour, this._atlas, exclude);
|
||||
public createBlockCollection(blocksToExclude: AppTypes.TNamespacedBlockName[]): TBlockCollection {
|
||||
const blocksNamesToUse = this._palette.getBlocks();
|
||||
{
|
||||
// Remove excluded blocks
|
||||
for (const blockToExclude of blocksToExclude) {
|
||||
const index = blocksNamesToUse.indexOf(blockToExclude);
|
||||
if (index != -1) {
|
||||
blocksNamesToUse.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const blocksToUse: TBlockCollection = {
|
||||
blocks: new Map(),
|
||||
cache: new Map(),
|
||||
};
|
||||
|
||||
const atlasBlocks = this._atlas.getBlocks();
|
||||
{
|
||||
// Only add block data for blocks in the palette
|
||||
atlasBlocks.forEach((atlasBlock, blockName) => {
|
||||
if (blocksNamesToUse.includes(blockName)) {
|
||||
blocksToUse.blocks.set(blockName, atlasBlock);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ASSERT(blocksToUse.blocks.size >= 1, 'Must have at least one block cached');
|
||||
return blocksToUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a colour into a Minecraft block.
|
||||
* @param colour The colour that the returned block should match with.
|
||||
* @param resolution The colour accuracy, a uint8 from 1 to 255, inclusive.
|
||||
* @param blockToExclude A list of blocks that should not be used, this should be a subset of the palette blocks.
|
||||
* @returns
|
||||
*/
|
||||
public getBlock(colour: RGBA, blockCollection: TBlockCollection, resolution: RGBAUtil.TColourAccuracy) {
|
||||
const { colourHash, binnedColour } = RGBAUtil.bin(colour, resolution);
|
||||
|
||||
// If we've already calculated the block associated with this colour, return it.
|
||||
const cachedBlock = blockCollection.cache.get(colourHash);
|
||||
if (cachedBlock !== undefined) {
|
||||
return cachedBlock;
|
||||
}
|
||||
|
||||
// Find closest block in colour
|
||||
let minDistance = Infinity;
|
||||
let blockChoice: TOptional<TAtlasBlock>;
|
||||
{
|
||||
blockCollection.blocks.forEach((blockData) => {
|
||||
const colourDistance = RGBAUtil.squaredDistance(binnedColour, blockData.colour);
|
||||
if (colourDistance < minDistance) {
|
||||
minDistance = colourDistance;
|
||||
blockChoice = blockData;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (blockChoice !== undefined) {
|
||||
blockCollection.cache.set(colourHash, blockChoice);
|
||||
return blockChoice;
|
||||
}
|
||||
|
||||
ASSERT(false, 'Unreachable, always at least one possible block');
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ export class BlockMesh {
|
||||
ASSERT(palette !== undefined, 'Could not load palette');
|
||||
|
||||
const atlasPalette = new AtlasPalette(atlas, palette);
|
||||
const allBlockCollection = atlasPalette.createBlockCollection([]);
|
||||
const nonFallableBlockCollection = atlasPalette.createBlockCollection(this._fallableBlocks);
|
||||
|
||||
const blockAssigner = BlockAssignerFactory.GetAssigner(blockMeshParams.blockAssigner);
|
||||
|
||||
@ -74,7 +76,15 @@ export class BlockMesh {
|
||||
ProgressManager.Get.progress(taskHandle, voxelIndex / voxels.length);
|
||||
|
||||
const voxel = voxels[voxelIndex];
|
||||
let block = blockAssigner.assignBlock(atlasPalette, voxel.colour, voxel.position, blockMeshParams.colourSpace);
|
||||
|
||||
let block = blockAssigner.assignBlock(
|
||||
atlasPalette,
|
||||
voxel.colour,
|
||||
voxel.position,
|
||||
blockMeshParams.resolution,
|
||||
blockMeshParams.colourSpace,
|
||||
allBlockCollection,
|
||||
);
|
||||
|
||||
const isFallable = this._fallableBlocks.includes(block.name);
|
||||
const isSupported = this._voxelMesh.isVoxelAt(Vector3.add(voxel.position, new Vector3(0, -1, 0)));
|
||||
@ -87,8 +97,14 @@ export class BlockMesh {
|
||||
shouldReplace ||= (blockMeshParams.fallable === 'replace-falling' && isFallable && !isSupported);
|
||||
|
||||
if (shouldReplace) {
|
||||
const replacedBlock = blockAssigner.assignBlock(atlasPalette, voxel.colour, voxel.position, blockMeshParams.colourSpace, this._fallableBlocks);
|
||||
// LOG(`Replacing ${block.name} with ${replacedBlock.name}`);
|
||||
const replacedBlock = blockAssigner.assignBlock(
|
||||
atlasPalette,
|
||||
voxel.colour,
|
||||
voxel.position,
|
||||
blockMeshParams.resolution,
|
||||
ColourSpace.RGB,
|
||||
nonFallableBlockCollection,
|
||||
);
|
||||
block = replacedBlock;
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,51 @@ export namespace RGBAUtil {
|
||||
export function toArray(a: RGBA): number[] {
|
||||
return [a.r, a.g, a.b, a.a];
|
||||
}
|
||||
|
||||
export function bin(col: RGBA, resolution: TColourAccuracy) {
|
||||
const r = Math.floor(col.r * resolution);
|
||||
const g = Math.floor(col.g * resolution);
|
||||
const b = Math.floor(col.b * resolution);
|
||||
const a = Math.ceil(col.a * resolution);
|
||||
|
||||
let hash = r;
|
||||
hash = (hash << 8) + g;
|
||||
hash = (hash << 8) + b;
|
||||
hash = (hash << 8) + a;
|
||||
|
||||
const binnedColour: RGBA = {
|
||||
r: r / resolution,
|
||||
g: g / resolution,
|
||||
b: b / resolution,
|
||||
a: a / resolution,
|
||||
};
|
||||
|
||||
return {
|
||||
colourHash: hash,
|
||||
binnedColour: binnedColour,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a colour as a single number.
|
||||
* Note this will bin colours together.
|
||||
* @param col The colour to hash.
|
||||
* @param resolution An uint8, the larger the more accurate the hash.
|
||||
*/
|
||||
export function hash(col: RGBA, resolution: TColourAccuracy): number {
|
||||
const r = Math.floor(col.r * resolution);
|
||||
const g = Math.floor(col.g * resolution);
|
||||
const b = Math.floor(col.b * resolution);
|
||||
const a = Math.floor(col.a * resolution);
|
||||
|
||||
let hash = r;
|
||||
hash = (hash << 8) + g;
|
||||
hash = (hash << 8) + b;
|
||||
hash = (hash << 8) + a;
|
||||
return hash;
|
||||
}
|
||||
|
||||
export type TColourAccuracy = number;
|
||||
}
|
||||
|
||||
export namespace RGBAColours {
|
||||
|
@ -80,18 +80,17 @@ export class ObjExporter extends IExporter {
|
||||
indicesIndex += buffer.indices.data.length;
|
||||
});
|
||||
|
||||
const writeStream = fs.createWriteStream(filepath);
|
||||
|
||||
writeStream.write('# Created with ObjToSchematic\n');
|
||||
writeStream.write('# https://github.com/LucasDower/ObjToSchematic/\n\n');
|
||||
const file = fs.openSync(filepath, 'w');
|
||||
fs.writeSync(file, '# Created with ObjToSchematic\n');
|
||||
fs.writeSync(file, '# https://github.com/LucasDower/ObjToSchematic/\n\n');
|
||||
|
||||
if (positionData && normalData && texcoordData && indexData && blockTexcoordData) {
|
||||
const numTris = indexData.length / 3;
|
||||
// Add vertex data
|
||||
writeStream.write(`mtllib ${mtlRelativePath}\n`);
|
||||
writeStream.write(`o Object\n`);
|
||||
fs.writeSync(file, `mtllib ${mtlRelativePath}\n`);
|
||||
fs.writeSync(file, `o Object\n`);
|
||||
for (let i = 0; i < positionData.length / 3; ++i) {
|
||||
writeStream.write(`v ${positionData[3 * i + 0]} ${positionData[3 * i + 1]} ${positionData[3 * i + 2]}\n`);
|
||||
fs.writeSync(file, `v ${positionData[3 * i + 0]} ${positionData[3 * i + 1]} ${positionData[3 * i + 2]}\n`);
|
||||
}
|
||||
// Add texcoord data
|
||||
const atlasSize = blockMesh.getAtlas().getAtlasSize();
|
||||
@ -99,25 +98,25 @@ export class ObjExporter extends IExporter {
|
||||
// vec2 tex = v_blockTexcoord + (v_texcoord / (u_atlasSize * 3.0));
|
||||
const u = blockTexcoordData[2 * i + 0] + (texcoordData[2 * i + 0] / (atlasSize * 3.0));
|
||||
const v = blockTexcoordData[2 * i + 1] + (texcoordData[2 * i + 1] / (atlasSize * 3.0));
|
||||
writeStream.write(`vt ${u} ${1.0 - v}\n`);
|
||||
fs.writeSync(file, `vt ${u} ${1.0 - v}\n`);
|
||||
}
|
||||
// Add normal data
|
||||
for (let i = 0; i < normalData.length / 3; ++i) {
|
||||
writeStream.write(`vn ${normalData[3 * i + 0]} ${normalData[3 * i + 1]} ${normalData[3 * i + 2]}\n`);
|
||||
fs.writeSync(file, `vn ${normalData[3 * i + 0]} ${normalData[3 * i + 1]} ${normalData[3 * i + 2]}\n`);
|
||||
}
|
||||
|
||||
writeStream.write(`usemtl Default\n`);
|
||||
fs.writeSync(file, `usemtl Default\n`);
|
||||
// Add face data
|
||||
for (let i = 0; i < numTris * 3; i += 3) {
|
||||
const a = indexData[i + 0] + 1;
|
||||
const b = indexData[i + 1] + 1;
|
||||
const c = indexData[i + 2] + 1;
|
||||
writeStream.write(`f ${a}/${a}/${a} ${b}/${b}/${b} ${c}/${c}/${c}\n`);
|
||||
fs.writeSync(file, `f ${a}/${a}/${a} ${b}/${b}/${b} ${c}/${c}/${c}\n`);
|
||||
}
|
||||
// Export to file
|
||||
}
|
||||
|
||||
writeStream.end();
|
||||
fs.closeSync(file);
|
||||
}
|
||||
|
||||
private _exportMTL(filepathMTL: string, filepathTexture: string, blockMesh: BlockMesh) {
|
||||
|
10
src/math.ts
10
src/math.ts
@ -15,6 +15,14 @@ export namespace AppMath {
|
||||
export function degreesToRadians(degrees: number) {
|
||||
return degrees * (Math.PI / 180.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a float in [0, 1] to an int in [0, 255]
|
||||
* @param decimal A number in [0, 1]
|
||||
*/
|
||||
export function uint8(decimal: number) {
|
||||
return Math.floor(decimal * 255);
|
||||
}
|
||||
}
|
||||
|
||||
export const argMax = (array: [number]) => {
|
||||
@ -42,7 +50,7 @@ export const between = (value: number, min: number, max: number) => {
|
||||
};
|
||||
|
||||
export const mapRange = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
|
||||
return (value - fromMin)/(fromMax - fromMin) * (toMax - toMin) + toMin;
|
||||
return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin;
|
||||
};
|
||||
|
||||
export const wayThrough = (value: number, min: number, max: number) => {
|
||||
|
@ -2,10 +2,9 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { Atlas } from './atlas';
|
||||
import { RGBA, RGBAUtil } from './colour';
|
||||
import { StatusHandler } from './status';
|
||||
import { AppTypes, AppUtil, TOptional } from './util';
|
||||
import { AppError, ASSERT } from './util/error_util';
|
||||
import { ASSERT } from './util/error_util';
|
||||
import { LOG_WARN } from './util/log_util';
|
||||
import { AppPaths, PathUtil } from './util/path_util';
|
||||
|
||||
@ -119,42 +118,6 @@ export class Palette {
|
||||
return this._blocks.length;
|
||||
}
|
||||
|
||||
public getBlock(voxelColour: RGBA, atlas: Atlas, blocksToExclude?: AppTypes.TNamespacedBlockName[]) {
|
||||
const blocksToUse = this.getBlocks();
|
||||
const atlasBlocks = atlas.getBlocks();
|
||||
|
||||
// Remove excluded blocks
|
||||
if (blocksToExclude !== undefined) {
|
||||
for (const blockToExclude of blocksToExclude) {
|
||||
const index = blocksToUse.indexOf(blockToExclude);
|
||||
if (index != -1) {
|
||||
blocksToUse.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find closest block in colour
|
||||
let minDistance = Infinity;
|
||||
let blockChoice: TOptional<AppTypes.TNamespacedBlockName>;
|
||||
|
||||
for (const blockName of blocksToUse) {
|
||||
const blockData = atlasBlocks.get(blockName);
|
||||
ASSERT(blockData);
|
||||
|
||||
const colourDistance = RGBAUtil.squaredDistance(voxelColour, blockData.colour);
|
||||
if (colourDistance < minDistance) {
|
||||
minDistance = colourDistance;
|
||||
blockChoice = blockName;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockChoice !== undefined) {
|
||||
return atlasBlocks.get(blockChoice)!;
|
||||
}
|
||||
|
||||
throw new AppError('Could not find a suitable block');
|
||||
}
|
||||
|
||||
public getBlocks() {
|
||||
return this._blocks;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export class LabelElement {
|
||||
}
|
||||
|
||||
public generateHTML(): string {
|
||||
const description = this._description ? `<br><div style="font-weight: 300; font-size: 85%; color: var(--text-disabled);">
|
||||
const description = false && this._description ? `<br><div style="font-weight: 300; font-size: 85%; color: var(--text-disabled);">
|
||||
${this._description}
|
||||
</div>` : '';
|
||||
return `
|
||||
|
@ -26,7 +26,7 @@ export class SliderElement extends LabelledElement<number> {
|
||||
return `
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
<div class="slider-value" id="${this._id + '-value'}">
|
||||
${this._value}
|
||||
${this._value?.toFixed(this._decimals)}
|
||||
</div>
|
||||
<div class="new-slider" id="${this._id}" style="flex-grow: 1;">
|
||||
<div class="new-slider-bar" id="${this._id}-bar"style="width: ${norm * 100}%;">
|
||||
|
@ -77,7 +77,7 @@ export class UI {
|
||||
displayText: 'Off (faster)',
|
||||
},
|
||||
]),
|
||||
'multisampleColouring': new ComboBoxElement('Multisample colouring', [
|
||||
'multisampleColouring': new ComboBoxElement('Multisampling', [
|
||||
{
|
||||
id: 'on',
|
||||
displayText: 'On (recommended)',
|
||||
@ -150,8 +150,9 @@ export class UI {
|
||||
tooltip: 'Let the block fall',
|
||||
},
|
||||
]),
|
||||
'colourAccuracy': new SliderElement('Colour accuracy', 1, 8, 1, 5, 0.1),
|
||||
},
|
||||
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'fallable'],
|
||||
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'fallable', 'colourAccuracy'],
|
||||
submitButton: new ButtonElement('Assign blocks', () => {
|
||||
this._appContext.do(EAction.Assign);
|
||||
}),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { TBlockAssigners } from './assigners/assigners';
|
||||
import { FallableBehaviour } from './block_mesh';
|
||||
import { TBlockMeshBufferDescription, TMeshBufferDescription, TVoxelMeshBufferDescription } from './buffer';
|
||||
import { RGBAUtil } from './colour';
|
||||
import { TExporters } from './exporters/exporters';
|
||||
import { StatusMessage } from './status';
|
||||
import { TextureFiltering } from './texture';
|
||||
@ -94,6 +95,7 @@ export namespace AssignParams {
|
||||
blockAssigner: TBlockAssigners,
|
||||
colourSpace: ColourSpace,
|
||||
fallable: FallableBehaviour,
|
||||
resolution: RGBAUtil.TColourAccuracy,
|
||||
}
|
||||
|
||||
export type Output = {
|
||||
|
@ -22,6 +22,7 @@ const baseConfig: THeadlessConfig = {
|
||||
blockAssigner: 'ordered-dithering',
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: 'replace-falling',
|
||||
resolution: 32,
|
||||
},
|
||||
export: {
|
||||
filepath: '', // Must be an absolute path to the file (can be anywhere)
|
||||
|
@ -22,6 +22,7 @@ const baseConfig: THeadlessConfig = {
|
||||
blockAssigner: 'ordered-dithering',
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: 'replace-falling',
|
||||
resolution: 32,
|
||||
},
|
||||
export: {
|
||||
filepath: '', // Must be an absolute path to the file (can be anywhere)
|
||||
|
@ -22,6 +22,7 @@ const baseConfig: THeadlessConfig = {
|
||||
blockAssigner: 'ordered-dithering',
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: 'replace-falling',
|
||||
resolution: 32,
|
||||
},
|
||||
export: {
|
||||
filepath: '', // Must be an absolute path to the file (can be anywhere)
|
||||
|
@ -22,6 +22,7 @@ const baseConfig: THeadlessConfig = {
|
||||
blockAssigner: 'ordered-dithering',
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: 'replace-falling',
|
||||
resolution: 32,
|
||||
},
|
||||
export: {
|
||||
filepath: '', // Must be an absolute path to the file (can be anywhere)
|
||||
|
21
tests/random-dither.test.ts
Normal file
21
tests/random-dither.test.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { StatusHandler, StatusID } from '../src/status';
|
||||
import { AppPaths, PathUtil } from '../src/util/path_util';
|
||||
import { WorkerClient } from '../src/worker_client';
|
||||
import { headlessConfig } from '../tools/headless-config';
|
||||
import { TEST_PREAMBLE } from './preamble';
|
||||
|
||||
test('Random-dither', () => {
|
||||
TEST_PREAMBLE();
|
||||
|
||||
const config = headlessConfig;
|
||||
|
||||
config.import.filepath = PathUtil.join(AppPaths.Get.resources, './samples/skull.obj');
|
||||
config.assign.blockAssigner = 'random-dithering';
|
||||
|
||||
const worker = WorkerClient.Get;
|
||||
worker.import(headlessConfig.import);
|
||||
worker.voxelise(headlessConfig.voxelise);
|
||||
worker.assign(headlessConfig.assign);
|
||||
|
||||
expect(StatusHandler.Get.hasId(StatusID.SchematicUnsupportedBlocks)).toBe(false);
|
||||
});
|
@ -20,6 +20,7 @@ export const headlessConfig: THeadlessConfig = {
|
||||
blockAssigner: 'ordered-dithering',
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: 'replace-falling',
|
||||
resolution: 32,
|
||||
},
|
||||
export: {
|
||||
filepath: '/Users/lucasdower/Documents/out.obj', // Must be an absolute path to the file (can be anywhere)
|
||||
|
Loading…
Reference in New Issue
Block a user