Added transparency options in material section

* Fixed sampling texture colour with mismatched diffuse and alpha map dimensions
* Fixed opening directory of texture on Windows
This commit is contained in:
Lucas Dower 2023-01-22 22:11:27 +00:00
parent 3b2d126264
commit 458b2ddf58
No known key found for this signature in database
GPG Key ID: B3EE6B8499593605
12 changed files with 324 additions and 207 deletions

View File

@ -5,8 +5,7 @@ uniform vec3 u_cameraDir;
uniform sampler2D u_texture; uniform sampler2D u_texture;
uniform sampler2D u_alpha; uniform sampler2D u_alpha;
uniform bool u_useAlphaMap; uniform int u_alphaChannel;
uniform bool u_useAlphaChannel;
uniform float u_alphaFactor; uniform float u_alphaFactor;
uniform float u_fresnelExponent; uniform float u_fresnelExponent;
uniform float u_fresnelMix; uniform float u_fresnelMix;
@ -106,10 +105,17 @@ const float ditherThreshold[64] = float[64](
void main() { void main() {
vec2 tex = vec2(v_texcoord.x, 1.0 - v_texcoord.y); vec2 tex = vec2(v_texcoord.x, 1.0 - v_texcoord.y);
vec4 diffuse = texture2D(u_texture, tex).rgba; vec4 diffuse = texture2D(u_texture, tex).rgba;
vec4 alphaSample = texture2D(u_alpha, tex);
float alpha = diffuse.a; float alpha = 1.0;
if (u_useAlphaMap) { if (u_alphaChannel == 0) {
alpha = u_useAlphaChannel ? texture2D(u_alpha, tex).a : texture2D(u_alpha, tex).r; alpha = alphaSample.r;
} else if (u_alphaChannel == 1) {
alpha = alphaSample.g;
} else if (u_alphaChannel == 2) {
alpha = alphaSample.b;
} else if (u_alphaChannel == 3) {
alpha = alphaSample.a;
} }
alpha *= u_alphaFactor; alpha *= u_alphaFactor;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
res/static/debug_alpha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -279,6 +279,10 @@ export class AppContext {
.onChangeTypeDelegate(() => { .onChangeTypeDelegate(() => {
this._materialManager.changeMaterialType(materialName, MaterialType.solid); this._materialManager.changeMaterialType(materialName, MaterialType.solid);
this._updateMaterialsAction(); this._updateMaterialsAction();
})
.onChangeTransparencyTypeDelegate((newTransparency) => {
this._materialManager.changeTransparencyType(materialName, newTransparency);
this._updateMaterialsAction();
}); });
} }

View File

@ -5,6 +5,7 @@ import { RGBA, RGBAColours } from '../colour';
import { checkFractional, checkNaN } from '../math'; import { checkFractional, checkNaN } from '../math';
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh'; import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
import { StatusHandler } from '../status'; import { StatusHandler } from '../status';
import { EImageChannel, TTransparencyOptions } from '../texture';
import { UV } from '../util'; import { UV } from '../util';
import { AppError, ASSERT } from '../util/error_util'; import { AppError, ASSERT } from '../util/error_util';
import { LOG } from '../util/log_util'; import { LOG } from '../util/log_util';
@ -205,7 +206,7 @@ export class ObjImporter extends IImporter {
]; ];
private _currentColour: RGBA = RGBAColours.BLACK; private _currentColour: RGBA = RGBAColours.BLACK;
private _currentAlpha: number = 1.0; private _currentAlpha?: number;
private _currentTexture: string = ''; private _currentTexture: string = '';
private _currentTransparencyTexture: string = ''; private _currentTransparencyTexture: string = '';
private _materialReady: boolean = false; private _materialReady: boolean = false;
@ -237,7 +238,7 @@ export class ObjImporter extends IImporter {
const b = parseFloat(match.b); const b = parseFloat(match.b);
checkNaN(r, g, b); checkNaN(r, g, b);
checkFractional(r, g, b); checkFractional(r, g, b);
this._currentColour = { r: r, g: g, b: b, a: this._currentAlpha }; this._currentColour = { r: r, g: g, b: b, a: this._currentAlpha ?? 1.0 };
this._materialReady = true; this._materialReady = true;
}, },
}, },
@ -402,12 +403,34 @@ export class ObjImporter extends IImporter {
private _addCurrentMaterial() { private _addCurrentMaterial() {
if (this._materialReady && this._currentMaterialName !== '') { if (this._materialReady && this._currentMaterialName !== '') {
if (this._currentTexture !== '') { if (this._currentTexture !== '') {
let transparency: TTransparencyOptions;
if (this._currentTransparencyTexture === this._currentTexture) {
transparency = {
type: 'UseDiffuseMapAlphaChannel',
};
} else if (this._currentTransparencyTexture === '') {
if (this._currentAlpha === undefined) {
transparency = {
type: 'None',
};
} else {
transparency = {
type: 'UseAlphaValue',
alpha: this._currentAlpha,
};
}
} else {
transparency = {
type: 'UseAlphaMap',
path: this._currentTransparencyTexture,
channel: EImageChannel.R,
};
}
this._materials.set(this._currentMaterialName, { this._materials.set(this._currentMaterialName, {
type: MaterialType.textured, type: MaterialType.textured,
path: this._currentTexture, path: this._currentTexture,
alphaPath: this._currentTransparencyTexture === '' ? undefined : this._currentTransparencyTexture, transparency: transparency,
alphaFactor: this._currentAlpha,
canBeTextured: true,
extension: 'repeat', extension: 'repeat',
interpolation: 'linear', interpolation: 'linear',
needsAttention: false, needsAttention: false,
@ -420,7 +443,7 @@ export class ObjImporter extends IImporter {
r: this._currentColour.r, r: this._currentColour.r,
g: this._currentColour.g, g: this._currentColour.g,
b: this._currentColour.b, b: this._currentColour.b,
a: this._currentAlpha, a: this._currentAlpha ?? 1.0,
}, },
canBeTextured: false, canBeTextured: false,
needsAttention: false, needsAttention: false,

View File

@ -1,5 +1,6 @@
import { RGBAColours } from './colour'; import { RGBAColours } from './colour';
import { MaterialMap, MaterialType } from './mesh'; import { MaterialMap, MaterialType } from './mesh';
import { EImageChannel, TTransparencyTypes } from './texture';
import { ASSERT } from './util/error_util'; import { ASSERT } from './util/error_util';
import { AppPaths, PathUtil } from './util/path_util'; import { AppPaths, PathUtil } from './util/path_util';
@ -10,6 +11,38 @@ export class MaterialMapManager {
this.materials = materials; this.materials = materials;
} }
public changeTransparencyType(materialName: string, newTransparencyType: TTransparencyTypes) {
const currentMaterial = this.materials.get(materialName);
ASSERT(currentMaterial !== undefined, 'Cannot change transparency type of non-existent material');
ASSERT(currentMaterial.type === MaterialType.textured);
switch (newTransparencyType) {
case 'None':
currentMaterial.transparency = { type: 'None' };
break;
case 'UseAlphaMap':
currentMaterial.transparency = {
type: 'UseAlphaMap',
path: PathUtil.join(AppPaths.Get.static, 'debug_alpha.png'),
channel: EImageChannel.R,
};
break;
case 'UseAlphaValue':
currentMaterial.transparency = {
type: 'UseAlphaValue',
alpha: 1.0,
};
break;
case 'UseDiffuseMapAlphaChannel':
currentMaterial.transparency = {
type: 'UseDiffuseMapAlphaChannel',
};
break;
}
this.materials.set(materialName, currentMaterial);
}
/** /**
* Convert a material to a new type, i.e. textured to solid. * Convert a material to a new type, i.e. textured to solid.
* Will return if the material is already the given type. * Will return if the material is already the given type.
@ -24,19 +57,21 @@ export class MaterialMapManager {
switch (newMaterialType) { switch (newMaterialType) {
case MaterialType.solid: case MaterialType.solid:
ASSERT(currentMaterial.type === MaterialType.textured, 'Old material expect to be texture');
this.materials.set(materialName, { this.materials.set(materialName, {
type: MaterialType.solid, type: MaterialType.solid,
colour: RGBAColours.MAGENTA, colour: RGBAColours.MAGENTA,
canBeTextured: currentMaterial.canBeTextured, canBeTextured: true,
needsAttention: true, needsAttention: true,
}); });
break; break;
case MaterialType.textured: case MaterialType.textured:
ASSERT(currentMaterial.type === MaterialType.solid, 'Old material expect to be solid');
this.materials.set(materialName, { this.materials.set(materialName, {
type: MaterialType.textured, type: MaterialType.textured,
alphaFactor: 1.0, transparency: {
alphaPath: undefined, type: 'None',
canBeTextured: currentMaterial.canBeTextured, },
extension: 'repeat', extension: 'repeat',
interpolation: 'linear', interpolation: 'linear',
needsAttention: true, needsAttention: true,

View File

@ -5,7 +5,7 @@ import { Bounds } from './bounds';
import { RGBA, RGBAColours, RGBAUtil } from './colour'; import { RGBA, RGBAColours, RGBAUtil } from './colour';
import { degreesToRadians } from './math'; import { degreesToRadians } from './math';
import { StatusHandler } from './status'; import { StatusHandler } from './status';
import { Texture, TextureConverter } from './texture'; import { Texture, TextureConverter, TTransparencyOptions } from './texture';
import { Triangle, UVTriangle } from './triangle'; import { Triangle, UVTriangle } from './triangle';
import { getRandomID, UV } from './util'; import { getRandomID, UV } from './util';
import { AppError, ASSERT } from './util/error_util'; import { AppError, ASSERT } from './util/error_util';
@ -30,21 +30,20 @@ export interface Tri {
export enum MaterialType { solid, textured } export enum MaterialType { solid, textured }
/* eslint-enable */ /* eslint-enable */
type BaseMaterial = { type BaseMaterial = {
canBeTextured: boolean,
needsAttention: boolean, // True if the user should make edits to this material needsAttention: boolean, // True if the user should make edits to this material
} }
export type SolidMaterial = BaseMaterial & { export type SolidMaterial = BaseMaterial & {
type: MaterialType.solid, type: MaterialType.solid,
colour: RGBA, colour: RGBA,
canBeTextured: boolean,
} }
export type TexturedMaterial = BaseMaterial & { export type TexturedMaterial = BaseMaterial & {
type: MaterialType.textured, type: MaterialType.textured,
path: string, path: string,
alphaPath?: string,
alphaFactor: number,
interpolation: TTexelInterpolation, interpolation: TTexelInterpolation,
extension: TTexelExtension, extension: TTexelExtension,
transparency: TTransparencyOptions,
} }
export type MaterialMap = Map<string, SolidMaterial | TexturedMaterial>; export type MaterialMap = Map<string, SolidMaterial | TexturedMaterial>;
@ -57,7 +56,7 @@ export class Mesh {
public _tris!: Tri[]; public _tris!: Tri[];
private _materials!: MaterialMap; private _materials!: MaterialMap;
private _loadedTextures: { [materialName: string]: Texture }; private _loadedTextures: Map<string, Texture>;
public static desiredHeight = 8.0; public static desiredHeight = 8.0;
constructor(vertices: Vector3[], normals: Vector3[], uvs: UV[], tris: Tri[], materials: MaterialMap) { constructor(vertices: Vector3[], normals: Vector3[], uvs: UV[], tris: Tri[], materials: MaterialMap) {
@ -68,7 +67,7 @@ export class Mesh {
this._uvs = uvs; this._uvs = uvs;
this._tris = tris; this._tris = tris;
this._materials = materials; this._materials = materials;
this._loadedTextures = {}; this._loadedTextures = new Map();
} }
// TODO: Always check // TODO: Always check
@ -290,17 +289,12 @@ export class Mesh {
} }
private _loadTextures() { private _loadTextures() {
this._loadedTextures = {}; this._loadedTextures.clear();
for (const tri of this._tris) { this._materials.forEach((material, materialName) => {
const material = this._materials.get(tri.material); if (material.type === MaterialType.textured && !this._loadedTextures.has(materialName)) {
ASSERT(material !== undefined, 'Triangle uses a material that doesn\'t exist in the material map'); this._loadedTextures.set(materialName, new Texture(material.path, material.transparency));
if (material.type == MaterialType.textured) {
if (!(tri.material in this._loadedTextures)) {
this._loadedTextures[tri.material] = new Texture(material.path, material.alphaPath);
}
} }
} });
} }
public getVertices(triIndex: number) { public getVertices(triIndex: number) {
@ -393,11 +387,10 @@ export class Mesh {
ASSERT(material !== undefined, `Sampling material that does not exist: ${materialName}`); ASSERT(material !== undefined, `Sampling material that does not exist: ${materialName}`);
ASSERT(material.type === MaterialType.textured, 'Sampling texture material of non-texture material'); ASSERT(material.type === MaterialType.textured, 'Sampling texture material of non-texture material');
ASSERT(materialName in this._loadedTextures, 'Sampling texture that is not loaded'); const texture = this._loadedTextures.get(materialName);
ASSERT(texture !== undefined, 'Sampling texture that is not loaded');
const colour = this._loadedTextures[materialName].getRGBA(uv, material.interpolation, material.extension); return texture.getRGBA(uv, material.interpolation, material.extension);
colour.a *= material.alphaFactor;
return colour;
} }
public getTriangleCount(): number { public getTriangleCount(): number {

View File

@ -7,7 +7,7 @@ import { DebugGeometryTemplates } from './geometry';
import { MaterialType, SolidMaterial, TexturedMaterial } from './mesh'; import { MaterialType, SolidMaterial, TexturedMaterial } from './mesh';
import { RenderBuffer } from './render_buffer'; import { RenderBuffer } from './render_buffer';
import { ShaderManager } from './shaders'; import { ShaderManager } from './shaders';
import { Texture } from './texture'; import { EImageChannel, Texture } from './texture';
import { ASSERT } from './util/error_util'; import { ASSERT } from './util/error_util';
import { Vector3 } from './vector'; import { Vector3 } from './vector';
import { RenderMeshParams, RenderNextBlockMeshChunkParams, RenderNextVoxelMeshChunkParams } from './worker_types'; import { RenderMeshParams, RenderNextBlockMeshChunkParams, RenderNextVoxelMeshChunkParams } from './worker_types';
@ -30,10 +30,28 @@ enum EDebugBufferComponents {
} }
/* eslint-enable */ /* eslint-enable */
export type TextureMaterialRenderAddons = { /**
texture: WebGLTexture, alpha?: WebGLTexture, useAlphaChannel?: boolean, * Dedicated type for passing to shaders for solid materials
*/
type InternalSolidMaterial = {
type: MaterialType.solid,
colourArray: number[],
} }
/**
* Dedicated type for passing to shaders for textured materials
*/
type InternalTextureMaterial = {
type: MaterialType.textured,
diffuseTexture: WebGLTexture,
// The texture to sample alpha values from (if is using a texture map)
alphaTexture: WebGLTexture,
// What texture channel to sample the alpha value from
alphaChannel: EImageChannel,
// What alpha value to use (only used if using constant transparency mode)
alphaValue: number,
};
export class Renderer { export class Renderer {
public _gl: WebGLRenderingContext; public _gl: WebGLRenderingContext;
@ -48,7 +66,7 @@ export class Renderer {
private _modelsAvailable: number; private _modelsAvailable: number;
private _materialBuffers: Map<string, { private _materialBuffers: Map<string, {
material: SolidMaterial | (TexturedMaterial & TextureMaterialRenderAddons) material: InternalSolidMaterial | InternalTextureMaterial,
buffer: twgl.BufferInfo, buffer: twgl.BufferInfo,
numElements: number, numElements: number,
materialName: string, materialName: string,
@ -192,123 +210,77 @@ export class Renderer {
this.setModelToUse(MeshType.None); this.setModelToUse(MeshType.None);
} }
public recreateMaterialBuffer(materialName: string, material: SolidMaterial | TexturedMaterial) { private _createInternalMaterial(material: SolidMaterial | TexturedMaterial): (InternalSolidMaterial | InternalTextureMaterial) {
const oldBuffer = this._materialBuffers.get(materialName);
ASSERT(oldBuffer !== undefined);
if (material.type === MaterialType.solid) { if (material.type === MaterialType.solid) {
this._materialBuffers.set(materialName, { return {
buffer: oldBuffer.buffer, type: MaterialType.solid,
material: { colourArray: RGBAUtil.toArray(material.colour),
type: MaterialType.solid, };
colour: RGBAUtil.copy(material.colour),
needsAttention: material.needsAttention,
canBeTextured: material.canBeTextured,
},
numElements: oldBuffer.numElements,
materialName: materialName,
});
} else { } else {
this._materialBuffers.set(materialName, { const diffuseTexture = twgl.createTexture(this._gl, {
buffer: oldBuffer.buffer, src: material.path,
material: { min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
type: MaterialType.textured, mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
path: material.path, wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
canBeTextured: material.canBeTextured,
interpolation: material.interpolation,
extension: material.extension,
texture: twgl.createTexture(this._gl, {
src: material.path,
min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
}),
alphaFactor: material.alphaFactor,
alpha: material.alphaPath ? twgl.createTexture(this._gl, {
src: material.alphaPath,
min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
}) : undefined,
useAlphaChannel: material.alphaPath ? new Texture(material.path, material.alphaPath)._useAlphaChannel() : undefined,
needsAttention: material.needsAttention,
},
numElements: oldBuffer.numElements,
materialName: materialName,
}); });
const alphaTexture = material.transparency.type === 'UseAlphaMap' ? twgl.createTexture(this._gl, {
src: material.transparency.path,
min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
}) : diffuseTexture;
const alphaValue = material.transparency.type === 'UseAlphaValue' ?
material.transparency.alpha : 1.0;
let alphaChannel: EImageChannel = EImageChannel.MAX;
switch (material.transparency.type) {
case 'UseAlphaValue':
alphaChannel = EImageChannel.MAX;
break;
case 'UseDiffuseMapAlphaChannel':
alphaChannel = EImageChannel.A;
break;
case 'UseAlphaMap':
alphaChannel = material.transparency.channel;
break;
}
return {
type: MaterialType.textured,
diffuseTexture: diffuseTexture,
alphaTexture: alphaTexture,
alphaValue: alphaValue,
alphaChannel: alphaChannel,
};
} }
} }
public updateMeshMaterialTexture(materialName: string, material: TexturedMaterial) { public recreateMaterialBuffer(materialName: string, material: SolidMaterial | TexturedMaterial) {
this._materialBuffers.forEach((buffer) => { const oldBuffer = this._materialBuffers.get(materialName);
if (buffer.materialName === materialName) { ASSERT(oldBuffer !== undefined);
buffer.material = {
type: MaterialType.textured, const internalMaterial = this._createInternalMaterial(material);
path: material.path, this._materialBuffers.set(materialName, {
interpolation: material.interpolation, buffer: oldBuffer.buffer,
extension: material.extension, material: internalMaterial,
canBeTextured: material.canBeTextured, numElements: oldBuffer.numElements,
texture: twgl.createTexture(this._gl, { materialName: materialName,
src: material.path,
min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
}),
alphaFactor: material.alphaFactor,
alpha: material.alphaPath ? twgl.createTexture(this._gl, {
src: material.alphaPath,
min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
}) : undefined,
useAlphaChannel: material.alphaPath ? new Texture(material.path, material.alphaPath)._useAlphaChannel() : undefined,
needsAttention: material.needsAttention,
};
return;
}
}); });
} }
public useMesh(params: RenderMeshParams.Output) { public useMesh(params: RenderMeshParams.Output) {
this._materialBuffers = new Map(); this._materialBuffers = new Map();
for (const { material, buffer, numElements, materialName } of params.buffers) { for (const { material, buffer, numElements, materialName } of params.buffers) {
if (material.type === MaterialType.solid) { const internalMaterial = this._createInternalMaterial(material);
this._materialBuffers.set(materialName, { this._materialBuffers.set(materialName, {
buffer: twgl.createBufferInfoFromArrays(this._gl, buffer), buffer: twgl.createBufferInfoFromArrays(this._gl, buffer),
material: material, material: internalMaterial,
numElements: numElements, numElements: numElements,
materialName: materialName, materialName: materialName,
}); });
} else {
this._materialBuffers.set(materialName, {
buffer: twgl.createBufferInfoFromArrays(this._gl, buffer),
material: {
canBeTextured: material.canBeTextured,
type: MaterialType.textured,
interpolation: material.interpolation,
extension: material.extension,
path: material.path,
texture: twgl.createTexture(this._gl, {
src: material.path,
min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
}),
alphaFactor: material.alphaFactor,
alpha: material.alphaPath ? twgl.createTexture(this._gl, {
src: material.alphaPath,
min: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
mag: material.interpolation === 'linear' ? this._gl.LINEAR : this._gl.NEAREST,
wrap: material.extension === 'clamp' ? this._gl.CLAMP_TO_EDGE : this._gl.REPEAT,
}) : undefined,
useAlphaChannel: material.alphaPath ? new Texture(material.path, material.alphaPath)._useAlphaChannel() : undefined,
needsAttention: material.needsAttention,
},
numElements: numElements,
materialName: materialName,
});
}
} }
this._gridBuffers.x[MeshType.TriangleMesh] = DebugGeometryTemplates.gridX(params.dimensions); this._gridBuffers.x[MeshType.TriangleMesh] = DebugGeometryTemplates.gridX(params.dimensions);
@ -458,9 +430,6 @@ export class Renderer {
} }
} }
public parseRawMeshData(buffer: string, dimensions: Vector3) {
}
private _drawMesh() { private _drawMesh() {
this._materialBuffers.forEach((materialBuffer, materialName) => { this._materialBuffers.forEach((materialBuffer, materialName) => {
if (materialBuffer.material.type === MaterialType.textured) { if (materialBuffer.material.type === MaterialType.textured) {
@ -468,11 +437,10 @@ export class Renderer {
u_lightWorldPos: ArcballCamera.Get.getCameraPosition(-Math.PI/4, 0.0).toArray(), u_lightWorldPos: ArcballCamera.Get.getCameraPosition(-Math.PI/4, 0.0).toArray(),
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(), u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
u_worldInverseTranspose: ArcballCamera.Get.getWorldInverseTranspose(), u_worldInverseTranspose: ArcballCamera.Get.getWorldInverseTranspose(),
u_texture: materialBuffer.material.texture, u_texture: materialBuffer.material.diffuseTexture,
u_alpha: materialBuffer.material.alpha || materialBuffer.material.texture, u_alpha: materialBuffer.material.alphaTexture ?? materialBuffer.material.diffuseTexture,
u_useAlphaMap: materialBuffer.material.alpha !== undefined, u_alphaChannel: materialBuffer.material.alphaChannel,
u_useAlphaChannel: materialBuffer.material.useAlphaChannel, u_alphaFactor: materialBuffer.material.alphaValue,
u_alphaFactor: materialBuffer.material.alphaFactor,
u_cameraDir: ArcballCamera.Get.getCameraDirection().toArray(), u_cameraDir: ArcballCamera.Get.getCameraDirection().toArray(),
u_fresnelExponent: AppConfig.Get.FRESNEL_EXPONENT, u_fresnelExponent: AppConfig.Get.FRESNEL_EXPONENT,
u_fresnelMix: AppConfig.Get.FRESNEL_MIX, u_fresnelMix: AppConfig.Get.FRESNEL_MIX,
@ -482,7 +450,7 @@ export class Renderer {
u_lightWorldPos: ArcballCamera.Get.getCameraPosition(-Math.PI/4, 0.0).toArray(), u_lightWorldPos: ArcballCamera.Get.getCameraPosition(-Math.PI/4, 0.0).toArray(),
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(), u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
u_worldInverseTranspose: ArcballCamera.Get.getWorldInverseTranspose(), u_worldInverseTranspose: ArcballCamera.Get.getWorldInverseTranspose(),
u_fillColour: RGBAUtil.toArray(materialBuffer.material.colour), u_fillColour: materialBuffer.material.colourArray,
u_cameraDir: ArcballCamera.Get.getCameraDirection().toArray(), u_cameraDir: ArcballCamera.Get.getCameraDirection().toArray(),
u_fresnelExponent: AppConfig.Get.FRESNEL_EXPONENT, u_fresnelExponent: AppConfig.Get.FRESNEL_EXPONENT,
u_fresnelMix: AppConfig.Get.FRESNEL_MIX, u_fresnelMix: AppConfig.Get.FRESNEL_MIX,

View File

@ -34,18 +34,37 @@ type ImageData = {
height: number height: number
} }
/* eslint-disable */
export enum EImageChannel {
R = 0,
G = 1,
B = 2,
A = 3,
MAX = 4,
}
/* eslint-enable */
export type TTransparencyTypes = 'None' | 'UseDiffuseMapAlphaChannel' | 'UseAlphaValue' | 'UseAlphaMap';
export type TTransparencyOptions =
| { type: 'None' }
| { type: 'UseDiffuseMapAlphaChannel' }
| { type: 'UseAlphaValue', alpha: number }
| { type: 'UseAlphaMap', path: string, channel: EImageChannel };
export class Texture { export class Texture {
private _image: ImageData; private _image: ImageData;
private _alphaImage?: ImageData; private _alphaImage: ImageData;
private _transparency: TTransparencyOptions;
constructor(filename: string, transparencyFilename?: string) { constructor(diffusePath: string, transparency: TTransparencyOptions) {
ASSERT(path.isAbsolute(filename)); ASSERT(path.isAbsolute(diffusePath));
this._image = this._loadImageFile(filename); this._image = this._loadImageFile(diffusePath);
this._transparency = transparency;
if (transparencyFilename) { this._alphaImage = transparency.type === 'UseAlphaMap' ?
this._alphaImage = this._loadImageFile(transparencyFilename); this._loadImageFile(transparency.path) :
} this._image;
} }
private _loadImageFile(filename: string): ImageData { private _loadImageFile(filename: string): ImageData {
@ -63,6 +82,7 @@ export class Texture {
this._useAlphaChannelValue = false; this._useAlphaChannelValue = false;
return jpeg.decode(data, { return jpeg.decode(data, {
maxMemoryUsageInMB: AppConfig.Get.MAXIMUM_IMAGE_MEM_ALLOC, maxMemoryUsageInMB: AppConfig.Get.MAXIMUM_IMAGE_MEM_ALLOC,
formatAsRGBA: true,
}); });
} }
/* /*
@ -101,19 +121,28 @@ export class Texture {
ASSERT(uv.v >= 0.0 && uv.v <= 1.0, 'Texcoord UV.v OOB'); ASSERT(uv.v >= 0.0 && uv.v <= 1.0, 'Texcoord UV.v OOB');
uv.v = 1.0 - uv.v; uv.v = 1.0 - uv.v;
if (interpolation === 'nearest') { const diffuse = (interpolation === 'nearest') ?
return this._getNearestRGBA(uv); this._getNearestRGBA(this._image, uv) :
} else { this._getLinearRGBA(this._image, uv);
return this._getLinearRGBA(uv);
} const alpha = (interpolation === 'nearest') ?
this._getNearestRGBA(this._alphaImage, uv) :
this._getLinearRGBA(this._alphaImage, uv);
return {
r: diffuse.r,
g: diffuse.g,
b: diffuse.b,
a: alpha.a,
};
} }
/** /**
* UV is assumed to be in [0, 1] range. * UV is assumed to be in [0, 1] range.
*/ */
private _getLinearRGBA(uv: UV): RGBA { private _getLinearRGBA(image: ImageData, uv: UV): RGBA {
const x = uv.u * this._image.width; const x = uv.u * image.width;
const y = uv.v * this._image.height; const y = uv.v * image.height;
const xLeft = Math.floor(x); const xLeft = Math.floor(x);
const xRight = xLeft + 1; const xRight = xLeft + 1;
@ -127,12 +156,12 @@ export class Texture {
return RGBAColours.MAGENTA; return RGBAColours.MAGENTA;
} }
const A = this._getFromXY(xLeft, yUp); const A = Texture._sampleImage(this._image, xLeft, yUp);
const B = this._getFromXY(xRight, yUp); const B = Texture._sampleImage(this._image, xRight, yUp);
const AB = RGBAUtil.lerp(A, B, u); const AB = RGBAUtil.lerp(A, B, u);
const C = this._getFromXY(xLeft, yDown); const C = Texture._sampleImage(this._image, xLeft, yDown);
const D = this._getFromXY(xRight, yDown); const D = Texture._sampleImage(this._image, xRight, yDown);
const CD = RGBAUtil.lerp(C, D, u); const CD = RGBAUtil.lerp(C, D, u);
return RGBAUtil.lerp(AB, CD, v); return RGBAUtil.lerp(AB, CD, v);
@ -141,27 +170,20 @@ export class Texture {
/** /**
* UV is assumed to be in [0, 1] range. * UV is assumed to be in [0, 1] range.
*/ */
private _getNearestRGBA(uv: UV): RGBA { private _getNearestRGBA(image: ImageData, uv: UV): RGBA {
const x = Math.floor(uv.u * this._image.width); const diffuseX = Math.floor(uv.u * image.width);
const y = Math.floor(uv.v * this._image.height); const diffuseY = Math.floor(uv.v * image.height);
return this._getFromXY(x, y); return Texture._sampleImage(image, diffuseX, diffuseY);
} }
private _getFromXY(x: number, y: number): RGBA { private _sampleChannel(colour: RGBA, channel: EImageChannel) {
const diffuse = Texture._sampleImage(this._image, x, y); switch (channel) {
case EImageChannel.R: return colour.r;
if (this._alphaImage) { case EImageChannel.G: return colour.g;
const alpha = Texture._sampleImage(this._alphaImage, x, y); case EImageChannel.B: return colour.b;
return { case EImageChannel.A: return colour.a;
r: diffuse.r,
g: diffuse.g,
b: diffuse.b,
a: this._useAlphaChannel() ? alpha.a : alpha.r,
};
} }
return diffuse;
} }
public _useAlphaChannelValue?: boolean; public _useAlphaChannelValue?: boolean;

View File

@ -72,8 +72,8 @@ export class ImageElement extends ConfigUIElement<string, HTMLDivElement> {
const newPath = this.getValue(); const newPath = this.getValue();
const parsedPath = path.parse(newPath); const parsedPath = path.parse(newPath);
this._openElement.setEnabled(parsedPath.base !== 'debug.png'); this._openElement.setEnabled(parsedPath.base !== 'debug.png' && parsedPath.base !== 'debug_alpha.png');
this._switchElement.setActive(parsedPath.base === 'debug.png'); this._switchElement.setActive(parsedPath.base === 'debug.png' || parsedPath.base === 'debug_alpha.png');
const imageElement = UIUtil.getElementById(this._imageId) as HTMLImageElement; const imageElement = UIUtil.getElementById(this._imageId) as HTMLImageElement;
imageElement.src = newPath; imageElement.src = newPath;

View File

@ -1,5 +1,7 @@
import { MaterialType, TexturedMaterial } from '../../mesh'; import { MaterialType, TexturedMaterial } from '../../mesh';
import { EImageChannel, TTransparencyTypes } from '../../texture';
import { getRandomID } from '../../util'; import { getRandomID } from '../../util';
import { ASSERT } from '../../util/error_util';
import { ComboBoxElement } from './combobox'; import { ComboBoxElement } from './combobox';
import { ConfigUIElement } from './config_element'; import { ConfigUIElement } from './config_element';
import { ImageElement } from './image_element'; import { ImageElement } from './image_element';
@ -11,9 +13,12 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
private _colourId: string; private _colourId: string;
private _filteringElement: ComboBoxElement<'nearest' | 'linear'>; private _filteringElement: ComboBoxElement<'nearest' | 'linear'>;
private _wrapElement: ComboBoxElement<'clamp' | 'repeat'>; private _wrapElement: ComboBoxElement<'clamp' | 'repeat'>;
private _transparencyElement: ComboBoxElement<TTransparencyTypes>;
private _imageElement: ImageElement; private _imageElement: ImageElement;
private _typeElement: MaterialTypeElement; private _typeElement: MaterialTypeElement;
private _alphaElement: SliderElement; private _alphaValueElement?: SliderElement;
private _alphaMapElement?: ImageElement;
private _alphaChannelElement?: ComboBoxElement<EImageChannel>;
public constructor(materialName: string, material: TexturedMaterial) { public constructor(materialName: string, material: TexturedMaterial) {
super(material); super(material);
@ -32,17 +37,39 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
.setSmall() .setSmall()
.setDefaultValue(material.extension); .setDefaultValue(material.extension);
this._transparencyElement = new ComboBoxElement<TTransparencyTypes>()
.addItem({ payload: 'None', displayText: 'None' })
.addItem({ payload: 'UseAlphaMap', displayText: 'Alpha map' })
.addItem({ payload: 'UseAlphaValue', displayText: 'Alpha constant' })
.addItem({ payload: 'UseDiffuseMapAlphaChannel', displayText: 'Diffuse map alpha channel' })
.setSmall()
.setDefaultValue(material.transparency.type);
this._imageElement = new ImageElement(material.path); this._imageElement = new ImageElement(material.path);
this._typeElement = new MaterialTypeElement(MaterialType.textured); this._typeElement = new MaterialTypeElement(MaterialType.textured);
this._alphaElement = new SliderElement() switch (material.transparency.type) {
.setMin(0.0) case 'UseAlphaValue':
.setMax(1.0) this._alphaValueElement = new SliderElement()
.setDefaultValue(material.alphaFactor) .setMin(0.0)
.setDecimals(2) .setMax(1.0)
.setStep(0.01) .setDefaultValue(material.transparency.alpha)
.setSmall(); .setDecimals(2)
.setStep(0.01)
.setSmall();
break;
case 'UseAlphaMap':
this._alphaMapElement = new ImageElement(material.transparency.path);
this._alphaChannelElement = new ComboBoxElement<EImageChannel>()
.addItem({ payload: EImageChannel.R, displayText: 'Red' })
.addItem({ payload: EImageChannel.G, displayText: 'Green' })
.addItem({ payload: EImageChannel.B, displayText: 'Blue' })
.addItem({ payload: EImageChannel.A, displayText: 'Alpha' })
.setSmall()
.setDefaultValue(material.transparency.channel);
break;
}
} }
public override registerEvents(): void { public override registerEvents(): void {
@ -50,7 +77,10 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._typeElement.registerEvents(); this._typeElement.registerEvents();
this._filteringElement.registerEvents(); this._filteringElement.registerEvents();
this._wrapElement.registerEvents(); this._wrapElement.registerEvents();
this._alphaElement.registerEvents(); this._transparencyElement.registerEvents();
this._alphaValueElement?.registerEvents();
this._alphaMapElement?.registerEvents();
this._alphaChannelElement?.registerEvents();
this._imageElement.addValueChangedListener((newPath) => { this._imageElement.addValueChangedListener((newPath) => {
const material = this.getValue(); const material = this.getValue();
@ -71,14 +101,30 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._onChangeTypeDelegate?.(); this._onChangeTypeDelegate?.();
}); });
this._alphaElement.addValueChangedListener((newAlpha) => { this._alphaValueElement?.addValueChangedListener((newAlpha) => {
this.getValue().alphaFactor = newAlpha; const material = this.getValue();
ASSERT(material.transparency.type === 'UseAlphaValue');
material.transparency.alpha = newAlpha;
});
this._alphaMapElement?.addValueChangedListener((newPath) => {
const material = this.getValue();
ASSERT(material.transparency.type === 'UseAlphaMap');
material.transparency.path = newPath;
});
this._alphaChannelElement?.addValueChangedListener((newChannel) => {
const material = this.getValue();
ASSERT(material.transparency.type === 'UseAlphaMap');
material.transparency.channel = newChannel;
});
this._transparencyElement.addValueChangedListener((newTransparency) => {
this._onChangeTransparencyTypeDelegate?.(newTransparency);
}); });
} }
protected override _generateInnerHTML(): string { protected override _generateInnerHTML(): string {
const material = this.getValue();
const subproperties: string[] = []; const subproperties: string[] = [];
const addSubproperty = (key: string, value: string) => { const addSubproperty = (key: string, value: string) => {
subproperties.push(` subproperties.push(`
@ -94,10 +140,18 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
}; };
addSubproperty('Type', this._typeElement._generateInnerHTML()); addSubproperty('Type', this._typeElement._generateInnerHTML());
addSubproperty('Alpha', this._alphaElement._generateInnerHTML()); addSubproperty('Diffuse map', this._imageElement._generateInnerHTML());
addSubproperty('File', this._imageElement._generateInnerHTML());
addSubproperty('Filtering', this._filteringElement._generateInnerHTML()); addSubproperty('Filtering', this._filteringElement._generateInnerHTML());
addSubproperty('Wrap', this._wrapElement._generateInnerHTML()); addSubproperty('Wrap', this._wrapElement._generateInnerHTML());
addSubproperty('Transparency', this._transparencyElement._generateInnerHTML());
if (this._alphaMapElement !== undefined) {
ASSERT(this._alphaChannelElement !== undefined);
addSubproperty('Alpha map', this._alphaMapElement._generateInnerHTML());
addSubproperty('Channel', this._alphaChannelElement._generateInnerHTML());
}
if (this._alphaValueElement) {
addSubproperty('Alpha', this._alphaValueElement._generateInnerHTML());
}
return ` return `
<div class="subproperty-container"> <div class="subproperty-container">
@ -120,6 +174,10 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._typeElement.finalise(); this._typeElement.finalise();
this._filteringElement.finalise(); this._filteringElement.finalise();
this._wrapElement.finalise(); this._wrapElement.finalise();
this._transparencyElement.finalise();
this._alphaValueElement?.finalise();
this._alphaMapElement?.finalise();
this._alphaChannelElement?.finalise();
} }
private _onChangeTypeDelegate?: () => void; private _onChangeTypeDelegate?: () => void;
@ -127,4 +185,10 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._onChangeTypeDelegate = delegate; this._onChangeTypeDelegate = delegate;
return this; return this;
} }
private _onChangeTransparencyTypeDelegate?: (newTransparency: TTransparencyTypes) => void;
public onChangeTransparencyTypeDelegate(delegate: (newTransparency: TTransparencyTypes) => void) {
this._onChangeTransparencyTypeDelegate = delegate;
return this;
}
} }

View File

@ -1,5 +1,6 @@
import child from 'child_process'; import child from 'child_process';
import fs from 'fs'; import fs from 'fs';
import path from 'path';
import { LOGF } from './log_util'; import { LOGF } from './log_util';
@ -27,7 +28,8 @@ export namespace FileUtil {
child.exec(`open -R ${absolutePath}`); child.exec(`open -R ${absolutePath}`);
break; break;
case 'win32': case 'win32':
child.exec(`explorer /select,"${absolutePath}"`); const parsed = path.parse(absolutePath);
child.exec(`start ${parsed.dir}`);
} }
} }
} }