Added ordered dithering and config options

This commit is contained in:
Lucas Dower 2021-11-19 02:05:59 +00:00
parent b9df55cfd0
commit 33135e186d
6 changed files with 122 additions and 35 deletions

57
src/block_assigner.ts Normal file
View File

@ -0,0 +1,57 @@
import { BlockAtlas, BlockInfo } from "./block_atlas";
import { assert, RGB } from "./util";
import { Vector3 } from "./vector";
interface BlockAssigner {
assignBlock(voxelColour: RGB, voxelPosition: Vector3): BlockInfo;
}
export class BasicBlockAssigner implements BlockAssigner {
assignBlock(voxelColour: RGB, voxelPosition: Vector3): BlockInfo {
return BlockAtlas.Get.getBlock(voxelColour);
}
}
export class OrderedDitheringBlockAssigner implements BlockAssigner {
/** 4x4x4 */
private static _size = 4;
private static _threshold = 256/4;
private static _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 = OrderedDitheringBlockAssigner._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 OrderedDitheringBlockAssigner._mapMatrix[index] / (size * size * size);
}
assignBlock(voxelColour: RGB, voxelPosition: Vector3): BlockInfo {
const size = OrderedDitheringBlockAssigner._size;
const map = this._getThresholdValue(
Math.abs(voxelPosition.x % size),
Math.abs(voxelPosition.y % size),
Math.abs(voxelPosition.z % size)
);
const newVoxelColour = {
r: ((255 * voxelColour.r) + map * OrderedDitheringBlockAssigner._threshold) / 255,
g: ((255 * voxelColour.g) + map * OrderedDitheringBlockAssigner._threshold) / 255,
b: ((255 * voxelColour.b) + map * OrderedDitheringBlockAssigner._threshold) / 255
};
return BlockAtlas.Get.getBlock(newVoxelColour);
}
}

View File

@ -35,11 +35,17 @@ export enum Block {
export class BlockAtlas { export class BlockAtlas {
private readonly _cachedBlocks: HashMap<Vector3, number>; private _cachedBlocks: HashMap<Vector3, number>;
private readonly _blocks: Array<BlockInfo>; private readonly _blocks: Array<BlockInfo>;
public readonly _atlasSize: number; public readonly _atlasSize: number;
constructor() { private static _instance: BlockAtlas;
public static get Get() {
return this._instance || (this._instance = new this());
}
private constructor() {
this._cachedBlocks = new HashMap(1024); this._cachedBlocks = new HashMap(1024);
const _path = path.join(__dirname, "../resources/blocks.json"); const _path = path.join(__dirname, "../resources/blocks.json");

14
src/config.ts Normal file
View File

@ -0,0 +1,14 @@
// TODO: Replace with UI options
export namespace AppConfig {
/** Recommended, but slower */
export const AMBIENT_OCCLUSION_ENABLED = true;
/** Darkens corner even if corner block does not exist, recommended */
export const AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;
/** Recomended, better matches colours */
export const DITHERING_ENABLED = true;
}

View File

@ -11,13 +11,8 @@ import { RGB, UV, rgbToArray } from "./util";
import { VoxelManager } from "./voxel_manager"; import { VoxelManager } from "./voxel_manager";
import { Triangle } from "./triangle"; import { Triangle } from "./triangle";
import { Mesh, FillMaterial, TextureMaterial, MaterialType } from "./mesh"; import { Mesh, FillMaterial, TextureMaterial, MaterialType } from "./mesh";
import { FaceInfo } from "./block_atlas"; import { FaceInfo, BlockAtlas } from "./block_atlas";
import { AppConfig } from "./config"
/** Recommended, but slower */
const ENABLE_AMBIENT_OCCLUSION = true;
/** Darkens corner even if corner block does not exist, recommended */
const AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;
export class Renderer { export class Renderer {
@ -118,7 +113,7 @@ export class Renderer {
// If both edge blocks along this vertex exist, // If both edge blocks along this vertex exist,
// assume corner exists (even if it doesnt) // assume corner exists (even if it doesnt)
// (This is a stylistic choice) // (This is a stylistic choice)
if (numNeighbours == 2 && AMBIENT_OCCLUSION_OVERRIDE_CORNER) { if (numNeighbours == 2 && AppConfig.AMBIENT_OCCLUSION_OVERRIDE_CORNER) {
++numNeighbours; ++numNeighbours;
} else { } else {
const neighbourIndex = this._occlusionNeighboursIndices[f][v][2]; const neighbourIndex = this._occlusionNeighboursIndices[f][v][2];
@ -144,9 +139,9 @@ export class Renderer {
return blankOcclusions; return blankOcclusions;
} }
private _registerVoxel(centre: Vector3, voxelManager: VoxelManager, blockTexcoord: FaceInfo) { private _registerVoxel(centre: Vector3, blockTexcoord: FaceInfo) {
let occlusions: number[][]; let occlusions: number[][];
if (ENABLE_AMBIENT_OCCLUSION) { if (AppConfig.AMBIENT_OCCLUSION_ENABLED) {
occlusions = this._calculateOcclusions(centre); occlusions = this._calculateOcclusions(centre);
} else { } else {
occlusions = Renderer._getBlankOcclusions(); occlusions = Renderer._getBlankOcclusions();
@ -191,7 +186,6 @@ export class Renderer {
material.faces.forEach(face => { material.faces.forEach(face => {
const data = GeometryTemplates.getTriangleBufferData(face, false); const data = GeometryTemplates.getTriangleBufferData(face, false);
//console.log(data);
materialBuffer.add(data); materialBuffer.add(data);
}); });
@ -207,7 +201,7 @@ export class Renderer {
const voxelManager = VoxelManager.Get; const voxelManager = VoxelManager.Get;
this._atlasSize = voxelManager.blockAtlas._atlasSize; this._atlasSize = BlockAtlas.Get._atlasSize;
if (this._debug) { if (this._debug) {
voxelManager.voxels.forEach((voxel) => { voxelManager.voxels.forEach((voxel) => {
@ -219,7 +213,7 @@ export class Renderer {
const voxel = voxelManager.voxels[i]; const voxel = voxelManager.voxels[i];
//const colour = voxelManager.voxelColours[i]; //const colour = voxelManager.voxelColours[i];
const texcoord = voxelManager.voxelTexcoords[i]; const texcoord = voxelManager.voxelTexcoords[i];
this._registerVoxel(voxel.position, voxelManager, texcoord); this._registerVoxel(voxel.position, texcoord);
} }
} }
} }
@ -232,7 +226,6 @@ export class Renderer {
compile() { compile() {
this._registerDebug.compile(this._gl); this._registerDebug.compile(this._gl);
this._registerVoxels.compile(this._gl); this._registerVoxels.compile(this._gl);
//this._registerDefault.compile(this._gl);
this._materialBuffers.forEach((materialBuffer) => { this._materialBuffers.forEach((materialBuffer) => {
materialBuffer.buffer.compile(this._gl); materialBuffer.buffer.compile(this._gl);

View File

@ -15,6 +15,15 @@ export interface RGB {
b: number b: number
} }
export function getAverageColour(colours: Array<RGB>) {
let averageColour = colours.reduce((a, c) => { return { r: a.r + c.r, g: a.g + c.g, b: a.b + c.b } });
let n = colours.length;
averageColour.r /= n;
averageColour.g /= n;
averageColour.b /= n;
return averageColour;
}
export function rgbToArray(rgb: RGB) { export function rgbToArray(rgb: RGB) {
return [rgb.r, rgb.g, rgb.b]; return [rgb.r, rgb.g, rgb.b];
} }
@ -37,3 +46,9 @@ export interface Bounds {
maxY: number, maxY: number,
maxZ: number, maxZ: number,
} }
export function assert(condition: boolean, errorMessage: string = "Assertion Failed") {
if (!condition) {
throw Error(errorMessage);
}
}

View File

@ -2,11 +2,13 @@ import { Vector3 } from "./vector.js";
import { HashMap } from "./hash_map"; import { HashMap } from "./hash_map";
import { Texture } from "./texture"; import { Texture } from "./texture";
import { BlockAtlas, BlockInfo, FaceInfo } from "./block_atlas"; import { BlockAtlas, BlockInfo, FaceInfo } from "./block_atlas";
import { RGB } from "./util"; import { RGB, getAverageColour } from "./util";
import { Triangle } from "./triangle"; import { Triangle } from "./triangle";
import { Mesh, MaterialType } from "./mesh"; import { Mesh, MaterialType } from "./mesh";
import { triangleArea } from "./math"; import { triangleArea } from "./math";
import { Axes, generateRays, rayIntersectTriangle } from "./ray"; import { Axes, generateRays, rayIntersectTriangle } from "./ray";
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from "./block_assigner.js";
import { AppConfig } from "./config.js";
interface Block { interface Block {
position: Vector3; position: Vector3;
@ -22,7 +24,6 @@ export class VoxelManager {
public _voxelSize: number; public _voxelSize: number;
private voxelsHash: HashMap<Vector3, Block>; private voxelsHash: HashMap<Vector3, Block>;
public blockAtlas: BlockAtlas;
private _blockMode!: MaterialType; private _blockMode!: MaterialType;
private _currentTexture!: Texture; private _currentTexture!: Texture;
private _currentColour!: RGB; private _currentColour!: RGB;
@ -43,7 +44,6 @@ export class VoxelManager {
this.voxelTexcoords = []; this.voxelTexcoords = [];
this.voxelsHash = new HashMap(2048); this.voxelsHash = new HashMap(2048);
this.blockAtlas = new BlockAtlas();
this.blockPalette = []; this.blockPalette = [];
} }
@ -54,7 +54,7 @@ export class VoxelManager {
private _clearVoxels() { private _clearVoxels() {
this.voxels = []; this.voxels = [];
this.voxelTexcoords = []; this.voxelTexcoords = [];
this.blockPalette = [] this.blockPalette = [];
this.min = new Vector3( Infinity, Infinity, Infinity); this.min = new Vector3( Infinity, Infinity, Infinity);
this.max = new Vector3(-Infinity, -Infinity, -Infinity); this.max = new Vector3(-Infinity, -Infinity, -Infinity);
@ -66,33 +66,35 @@ export class VoxelManager {
return this.voxelsHash.has(pos); return this.voxelsHash.has(pos);
} }
public assignBlocks() { private _assignBlock(voxelIndex: number, block: BlockInfo) {
this.blockPalette = []; this.voxels[voxelIndex].block = block.name;
let meanSquaredError = 0.0;
for (let i = 0; i < this.voxels.length; ++i) {
let averageColour = this.voxels[i].colours!.reduce((a, c) => {return {r: a.r + c.r, g: a.g + c.g, b: a.b + c.b}})
let n = this.voxels[i].colours!.length;
averageColour.r /= n;
averageColour.g /= n;
averageColour.b /= n;
const block = this.blockAtlas.getBlock(averageColour);
const squaredError = Math.pow(255 * (block.colour.r - averageColour.r), 2) + Math.pow(255 * (block.colour.g - averageColour.g), 2) + Math.pow(255 * (block.colour.b - averageColour.b), 2);
meanSquaredError += squaredError;
this.voxels[i].block = block.name;
this.voxelTexcoords.push(block.faces); this.voxelTexcoords.push(block.faces);
if (!this.blockPalette.includes(block.name)) { if (!this.blockPalette.includes(block.name)) {
this.blockPalette.push(block.name); this.blockPalette.push(block.name);
} }
} }
public assignBlocks() {
this.blockPalette = [];
let meanSquaredError = 0.0;
for (let i = 0; i < this.voxels.length; ++i) {
const voxel = this.voxels[i];
const averageColour = getAverageColour(voxel.colours!);
const blockAssigner = AppConfig.DITHERING_ENABLED ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner();
const block = blockAssigner.assignBlock(averageColour, voxel.position);
const squaredError = Math.pow(255 * (block.colour.r - averageColour.r), 2) + Math.pow(255 * (block.colour.g - averageColour.g), 2) + Math.pow(255 * (block.colour.b - averageColour.b), 2);
meanSquaredError += squaredError;
this._assignBlock(i, block);
}
meanSquaredError /= this.voxels.length; meanSquaredError /= this.voxels.length;
console.log("Mean Squared Error:", meanSquaredError); console.log("Mean Squared Error:", meanSquaredError);
} }
public addVoxel(pos: Vector3, block: BlockInfo) { public addVoxel(pos: Vector3, block: BlockInfo) {
@ -167,7 +169,7 @@ export class VoxelManager {
} }
const voxelColour = this._getVoxelColour(triangle, Vector3.mulScalar(voxelPosition, voxelSize)); const voxelColour = this._getVoxelColour(triangle, Vector3.mulScalar(voxelPosition, voxelSize));
const block = this.blockAtlas.getBlock(voxelColour); const block = BlockAtlas.Get.getBlock(voxelColour);
this.addVoxel(voxelPosition, block); this.addVoxel(voxelPosition, block);
} }