mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2024-12-21 03:09:14 +08:00
Added ordered dithering and config options
This commit is contained in:
parent
b9df55cfd0
commit
33135e186d
57
src/block_assigner.ts
Normal file
57
src/block_assigner.ts
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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
14
src/config.ts
Normal 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;
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
15
src/util.ts
15
src/util.ts
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _assignBlock(voxelIndex: number, block: BlockInfo) {
|
||||||
|
this.voxels[voxelIndex].block = block.name;
|
||||||
|
this.voxelTexcoords.push(block.faces);
|
||||||
|
|
||||||
|
if (!this.blockPalette.includes(block.name)) {
|
||||||
|
this.blockPalette.push(block.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public assignBlocks() {
|
public assignBlocks() {
|
||||||
this.blockPalette = [];
|
this.blockPalette = [];
|
||||||
let meanSquaredError = 0.0;
|
let meanSquaredError = 0.0;
|
||||||
|
|
||||||
for (let i = 0; i < this.voxels.length; ++i) {
|
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}})
|
const voxel = this.voxels[i];
|
||||||
let n = this.voxels[i].colours!.length;
|
|
||||||
averageColour.r /= n;
|
const averageColour = getAverageColour(voxel.colours!);
|
||||||
averageColour.g /= n;
|
|
||||||
averageColour.b /= n;
|
const blockAssigner = AppConfig.DITHERING_ENABLED ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner();
|
||||||
const block = this.blockAtlas.getBlock(averageColour);
|
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);
|
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;
|
meanSquaredError += squaredError;
|
||||||
|
|
||||||
this.voxels[i].block = block.name;
|
this._assignBlock(i, block);
|
||||||
this.voxelTexcoords.push(block.faces);
|
|
||||||
|
|
||||||
|
|
||||||
if (!this.blockPalette.includes(block.name)) {
|
|
||||||
this.blockPalette.push(block.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user