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 {
|
||||
|
||||
private readonly _cachedBlocks: HashMap<Vector3, number>;
|
||||
private _cachedBlocks: HashMap<Vector3, number>;
|
||||
private readonly _blocks: Array<BlockInfo>;
|
||||
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);
|
||||
|
||||
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 { Triangle } from "./triangle";
|
||||
import { Mesh, FillMaterial, TextureMaterial, MaterialType } from "./mesh";
|
||||
import { FaceInfo } from "./block_atlas";
|
||||
|
||||
/** Recommended, but slower */
|
||||
const ENABLE_AMBIENT_OCCLUSION = true;
|
||||
|
||||
/** Darkens corner even if corner block does not exist, recommended */
|
||||
const AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;
|
||||
import { FaceInfo, BlockAtlas } from "./block_atlas";
|
||||
import { AppConfig } from "./config"
|
||||
|
||||
export class Renderer {
|
||||
|
||||
@ -118,7 +113,7 @@ export class Renderer {
|
||||
// If both edge blocks along this vertex exist,
|
||||
// assume corner exists (even if it doesnt)
|
||||
// (This is a stylistic choice)
|
||||
if (numNeighbours == 2 && AMBIENT_OCCLUSION_OVERRIDE_CORNER) {
|
||||
if (numNeighbours == 2 && AppConfig.AMBIENT_OCCLUSION_OVERRIDE_CORNER) {
|
||||
++numNeighbours;
|
||||
} else {
|
||||
const neighbourIndex = this._occlusionNeighboursIndices[f][v][2];
|
||||
@ -144,9 +139,9 @@ export class Renderer {
|
||||
return blankOcclusions;
|
||||
}
|
||||
|
||||
private _registerVoxel(centre: Vector3, voxelManager: VoxelManager, blockTexcoord: FaceInfo) {
|
||||
private _registerVoxel(centre: Vector3, blockTexcoord: FaceInfo) {
|
||||
let occlusions: number[][];
|
||||
if (ENABLE_AMBIENT_OCCLUSION) {
|
||||
if (AppConfig.AMBIENT_OCCLUSION_ENABLED) {
|
||||
occlusions = this._calculateOcclusions(centre);
|
||||
} else {
|
||||
occlusions = Renderer._getBlankOcclusions();
|
||||
@ -191,7 +186,6 @@ export class Renderer {
|
||||
|
||||
material.faces.forEach(face => {
|
||||
const data = GeometryTemplates.getTriangleBufferData(face, false);
|
||||
//console.log(data);
|
||||
materialBuffer.add(data);
|
||||
});
|
||||
|
||||
@ -207,7 +201,7 @@ export class Renderer {
|
||||
|
||||
const voxelManager = VoxelManager.Get;
|
||||
|
||||
this._atlasSize = voxelManager.blockAtlas._atlasSize;
|
||||
this._atlasSize = BlockAtlas.Get._atlasSize;
|
||||
|
||||
if (this._debug) {
|
||||
voxelManager.voxels.forEach((voxel) => {
|
||||
@ -219,7 +213,7 @@ export class Renderer {
|
||||
const voxel = voxelManager.voxels[i];
|
||||
//const colour = voxelManager.voxelColours[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() {
|
||||
this._registerDebug.compile(this._gl);
|
||||
this._registerVoxels.compile(this._gl);
|
||||
//this._registerDefault.compile(this._gl);
|
||||
|
||||
this._materialBuffers.forEach((materialBuffer) => {
|
||||
materialBuffer.buffer.compile(this._gl);
|
||||
|
15
src/util.ts
15
src/util.ts
@ -15,6 +15,15 @@ export interface RGB {
|
||||
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) {
|
||||
return [rgb.r, rgb.g, rgb.b];
|
||||
}
|
||||
@ -37,3 +46,9 @@ export interface Bounds {
|
||||
maxY: 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 { Texture } from "./texture";
|
||||
import { BlockAtlas, BlockInfo, FaceInfo } from "./block_atlas";
|
||||
import { RGB } from "./util";
|
||||
import { RGB, getAverageColour } from "./util";
|
||||
import { Triangle } from "./triangle";
|
||||
import { Mesh, MaterialType } from "./mesh";
|
||||
import { triangleArea } from "./math";
|
||||
import { Axes, generateRays, rayIntersectTriangle } from "./ray";
|
||||
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from "./block_assigner.js";
|
||||
import { AppConfig } from "./config.js";
|
||||
|
||||
interface Block {
|
||||
position: Vector3;
|
||||
@ -22,7 +24,6 @@ export class VoxelManager {
|
||||
public _voxelSize: number;
|
||||
|
||||
private voxelsHash: HashMap<Vector3, Block>;
|
||||
public blockAtlas: BlockAtlas;
|
||||
private _blockMode!: MaterialType;
|
||||
private _currentTexture!: Texture;
|
||||
private _currentColour!: RGB;
|
||||
@ -43,7 +44,6 @@ export class VoxelManager {
|
||||
this.voxelTexcoords = [];
|
||||
|
||||
this.voxelsHash = new HashMap(2048);
|
||||
this.blockAtlas = new BlockAtlas();
|
||||
this.blockPalette = [];
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ export class VoxelManager {
|
||||
private _clearVoxels() {
|
||||
this.voxels = [];
|
||||
this.voxelTexcoords = [];
|
||||
this.blockPalette = []
|
||||
this.blockPalette = [];
|
||||
|
||||
this.min = new Vector3( Infinity, Infinity, Infinity);
|
||||
this.max = new Vector3(-Infinity, -Infinity, -Infinity);
|
||||
@ -66,33 +66,35 @@ export class VoxelManager {
|
||||
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() {
|
||||
this.blockPalette = [];
|
||||
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 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.voxels[i].block = block.name;
|
||||
this.voxelTexcoords.push(block.faces);
|
||||
|
||||
|
||||
if (!this.blockPalette.includes(block.name)) {
|
||||
this.blockPalette.push(block.name);
|
||||
}
|
||||
this._assignBlock(i, block);
|
||||
}
|
||||
|
||||
meanSquaredError /= this.voxels.length;
|
||||
console.log("Mean Squared Error:", meanSquaredError);
|
||||
|
||||
}
|
||||
|
||||
public addVoxel(pos: Vector3, block: BlockInfo) {
|
||||
@ -167,7 +169,7 @@ export class VoxelManager {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user