From bb11207e705185f59bac02a4eb9b9c543e7f4323 Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Sun, 13 Feb 2022 15:23:23 +0000 Subject: [PATCH] Added bilinear texture filtering for voxel colours --- src/app_context.ts | 4 ++- src/math.ts | 5 ++++ src/texture.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++-- src/ui/layout.ts | 8 ++++-- src/voxel_mesh.ts | 16 ++++++------ 5 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/app_context.ts b/src/app_context.ts index fd844cb..18b93da 100644 --- a/src/app_context.ts +++ b/src/app_context.ts @@ -8,6 +8,7 @@ import { ASSERT, CustomError, CustomWarning, LOG, LOG_ERROR } from './util'; import { remote } from 'electron'; import { VoxelMesh } from './voxel_mesh'; import { BlockMesh } from './block_mesh'; +import { TextureFiltering } from './texture'; /* eslint-disable */ export enum ActionReturnType { @@ -156,10 +157,11 @@ export class AppContext { const desiredHeight = UI.Get.layout.build.elements.height.getValue(); const ambientOcclusion = UI.Get.layout.build.elements.ambientOcclusion.getValue() === 'on'; const multisampleColouring = UI.Get.layout.build.elements.multisampleColouring.getValue() === 'on'; + const textureFiltering = UI.Get.layout.build.elements.textureFiltering.getValue() === 'nearest' ? TextureFiltering.Nearest : TextureFiltering.Linear; ASSERT(this._loadedMesh); this._loadedVoxelMesh = new VoxelMesh(desiredHeight); - this._loadedVoxelMesh.voxelise(this._loadedMesh, multisampleColouring); + this._loadedVoxelMesh.voxelise(this._loadedMesh, multisampleColouring, textureFiltering); Renderer.Get.useVoxelMesh(this._loadedVoxelMesh, ambientOcclusion); } diff --git a/src/math.ts b/src/math.ts index ffba1e8..b67c6e4 100644 --- a/src/math.ts +++ b/src/math.ts @@ -33,4 +33,9 @@ export const roundToNearest = (value: number, base: number) => { return Math.round(value / base) * base; }; +export const wayThrough = (value: number, min: number, max: number) => { + // ASSERT(value >= min && value <= max); + return (value - min) / (max - min); +}; + export const degreesToRadians = Math.PI / 180; diff --git a/src/texture.ts b/src/texture.ts index 434ea84..b25c315 100644 --- a/src/texture.ts +++ b/src/texture.ts @@ -1,10 +1,12 @@ -import { UV, ASSERT, CustomError } from './util'; +import { UV, ASSERT, CustomError, LOG } from './util'; import { RGB } from './util'; import * as fs from 'fs'; import * as jpeg from 'jpeg-js'; import { PNG } from 'pngjs'; import path from 'path'; +import { Vector3 } from './vector'; +import { clamp, wayThrough } from './math'; /* eslint-disable */ export enum TextureFormat { @@ -13,6 +15,13 @@ export enum TextureFormat { } /* eslint-enable */ +/* eslint-disable */ +export enum TextureFiltering { + Linear, + Nearest +} +/* eslint-enable */ + export class Texture { private _image: { data: Buffer, @@ -40,10 +49,58 @@ export class Texture { } } - getRGB(uv: UV): RGB { + getRGB(uv: UV, filtering: TextureFiltering): RGB { + if (filtering === TextureFiltering.Nearest) { + return this._getNearestRGB(uv); + } else { + return this._getLinearRGB(uv); + } + } + + private _getLinearRGB(uv: UV): RGB { + uv.v = 1.0 - uv.v; + if (uv.u === 0 || uv.u === 1 || uv.v === 0 || uv.v === 1) { + LOG('bad'); + } + + const x = uv.u * this._image.width; + const y = uv.v * this._image.height; + + const xL = Math.floor(x); + const xU = Math.ceil(x); + const yL = Math.floor(y); + const yU = Math.ceil(y); + + const u = wayThrough(x, xL, xU); + const v = wayThrough(y, yL, yU); + ASSERT(u >= 0.0 && u <= 1.0 && v >= 0.0 && v <= 1.0); + + const A = this._getFromXY(xL, yU).toVector3(); + const B = this._getFromXY(xU, yU).toVector3(); + const midAB = Vector3.mulScalar(B, u).add(Vector3.mulScalar(A, 1.0-u)); + + const C = this._getFromXY(xL, yL).toVector3(); + const D = this._getFromXY(xU, yL).toVector3(); + const midCD = Vector3.mulScalar(D, u).add(Vector3.mulScalar(C, 1.0-u)); + + const mid = Vector3.mulScalar(midAB, v).add(Vector3.mulScalar(midCD, 1.0-v)); + if (mid.equals(RGB.black.toVector3())) { + LOG('bad'); + } + return RGB.fromVector3(mid); + } + + private _getNearestRGB(uv: UV): RGB { const x = Math.floor(uv.u * this._image.width); const y = Math.floor((1 - uv.v) * this._image.height); + return this._getFromXY(x, y); + } + + private _getFromXY(x: number, y: number): RGB { + x = clamp(x, 0, this._image.width - 1); + y = clamp(y, 0, this._image.height - 1); + const index = 4 * (this._image.width * y + x); const rgba = this._image.data.slice(index, index + 4); diff --git a/src/ui/layout.ts b/src/ui/layout.ts index fb2186a..4962fc1 100644 --- a/src/ui/layout.ts +++ b/src/ui/layout.ts @@ -46,12 +46,16 @@ export class UI { { id: 'on', displayText: 'On (recommended)' }, { id: 'off', displayText: 'Off (faster)' }, ]), - 'multisampleColouring': new ComboBoxElement('Multisample Colouring', [ + 'multisampleColouring': new ComboBoxElement('Multisample colouring', [ { id: 'on', displayText: 'On (recommended)' }, { id: 'off', displayText: 'Off (faster)' }, ]), + 'textureFiltering': new ComboBoxElement('Texture filtering', [ + { id: 'linear', displayText: 'Linear (recommended)' }, + { id: 'nearest', displayText: 'Nearest (faster)' }, + ]), }, - elementsOrder: ['height', 'ambientOcclusion', 'multisampleColouring'], + elementsOrder: ['height', 'ambientOcclusion', 'multisampleColouring', 'textureFiltering'], submitButton: new ButtonElement('Voxelise mesh', () => { AppContext.Get.do(Action.Voxelise); }), diff --git a/src/voxel_mesh.ts b/src/voxel_mesh.ts index 3458b1d..6f97162 100644 --- a/src/voxel_mesh.ts +++ b/src/voxel_mesh.ts @@ -5,7 +5,7 @@ import { HashMap } from './hash_map'; import { MaterialType, Mesh, SolidMaterial, TexturedMaterial } from './mesh'; import { OcclusionManager } from './occlusion'; import { Axes, generateRays, rayIntersectTriangle } from './ray'; -import { Texture } from './texture'; +import { Texture, TextureFiltering } from './texture'; import { Triangle, UVTriangle } from './triangle'; import { Bounds, LOG, RGB, UV } from './util'; import { Vector3 } from './vector'; @@ -41,7 +41,7 @@ export class VoxelMesh { return this._voxelsHash.has(pos); } - public voxelise(mesh: Mesh, multisampleColouring: boolean) { + public voxelise(mesh: Mesh, multisampleColouring: boolean, filtering: TextureFiltering) { LOG('Voxelising'); mesh.tris.forEach((tri, index) => { @@ -52,11 +52,11 @@ export class VoxelMesh { } } const uvTriangle = mesh.getUVTriangle(index); - this._voxeliseTri(uvTriangle, material, tri.material, multisampleColouring); + this._voxeliseTri(uvTriangle, material, tri.material, multisampleColouring, filtering); }); } - private _voxeliseTri(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, multisampleColouring: boolean) { + private _voxeliseTri(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, multisampleColouring: boolean, filtering: TextureFiltering) { const v0Scaled = Vector3.divScalar(triangle.v0, this._voxelSize); const v1Scaled = Vector3.divScalar(triangle.v1, this._voxelSize); const v2Scaled = Vector3.divScalar(triangle.v2, this._voxelSize); @@ -83,18 +83,18 @@ export class VoxelMesh { const samples: RGB[] = []; for (let i = 0; i < AppConfig.MULTISAMPLE_COUNT; ++i) { const samplePosition = Vector3.mulScalar(Vector3.add(voxelPosition, Vector3.random().addScalar(-0.5)), this._voxelSize); - samples.push(this._getVoxelColour(triangle, material, materialName, samplePosition)); + samples.push(this._getVoxelColour(triangle, material, materialName, samplePosition, filtering)); } voxelColour = RGB.averageFrom(samples); } else { - voxelColour = this._getVoxelColour(triangle, material, materialName, Vector3.mulScalar(voxelPosition, this._voxelSize)); + voxelColour = this._getVoxelColour(triangle, material, materialName, Vector3.mulScalar(voxelPosition, this._voxelSize), filtering); } this._addVoxel(voxelPosition, voxelColour); } }); } - private _getVoxelColour(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, location: Vector3): RGB { + private _getVoxelColour(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, location: Vector3, filtering: TextureFiltering): RGB { if (material.type == MaterialType.solid) { return material.colour; } @@ -113,7 +113,7 @@ export class VoxelMesh { triangle.uv0.v * w0 + triangle.uv1.v * w1 + triangle.uv2.v * w2, ); - return this._loadedTextures[materialName].getRGB(uv); + return this._loadedTextures[materialName].getRGB(uv, filtering); } private _addVoxel(pos: Vector3, colour: RGB) {