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_alpha;
uniform bool u_useAlphaMap;
uniform bool u_useAlphaChannel;
uniform int u_alphaChannel;
uniform float u_alphaFactor;
uniform float u_fresnelExponent;
uniform float u_fresnelMix;
@ -106,10 +105,17 @@ const float ditherThreshold[64] = float[64](
void main() {
vec2 tex = vec2(v_texcoord.x, 1.0 - v_texcoord.y);
vec4 diffuse = texture2D(u_texture, tex).rgba;
vec4 alphaSample = texture2D(u_alpha, tex);
float alpha = diffuse.a;
if (u_useAlphaMap) {
alpha = u_useAlphaChannel ? texture2D(u_alpha, tex).a : texture2D(u_alpha, tex).r;
float alpha = 1.0;
if (u_alphaChannel == 0) {
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;

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(() => {
this._materialManager.changeMaterialType(materialName, MaterialType.solid);
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 { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
import { StatusHandler } from '../status';
import { EImageChannel, TTransparencyOptions } from '../texture';
import { UV } from '../util';
import { AppError, ASSERT } from '../util/error_util';
import { LOG } from '../util/log_util';
@ -205,7 +206,7 @@ export class ObjImporter extends IImporter {
];
private _currentColour: RGBA = RGBAColours.BLACK;
private _currentAlpha: number = 1.0;
private _currentAlpha?: number;
private _currentTexture: string = '';
private _currentTransparencyTexture: string = '';
private _materialReady: boolean = false;
@ -237,7 +238,7 @@ export class ObjImporter extends IImporter {
const b = parseFloat(match.b);
checkNaN(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;
},
},
@ -402,12 +403,34 @@ export class ObjImporter extends IImporter {
private _addCurrentMaterial() {
if (this._materialReady && this._currentMaterialName !== '') {
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, {
type: MaterialType.textured,
path: this._currentTexture,
alphaPath: this._currentTransparencyTexture === '' ? undefined : this._currentTransparencyTexture,
alphaFactor: this._currentAlpha,
canBeTextured: true,
transparency: transparency,
extension: 'repeat',
interpolation: 'linear',
needsAttention: false,
@ -420,7 +443,7 @@ export class ObjImporter extends IImporter {
r: this._currentColour.r,
g: this._currentColour.g,
b: this._currentColour.b,
a: this._currentAlpha,
a: this._currentAlpha ?? 1.0,
},
canBeTextured: false,
needsAttention: false,

View File

@ -1,5 +1,6 @@
import { RGBAColours } from './colour';
import { MaterialMap, MaterialType } from './mesh';
import { EImageChannel, TTransparencyTypes } from './texture';
import { ASSERT } from './util/error_util';
import { AppPaths, PathUtil } from './util/path_util';
@ -10,6 +11,38 @@ export class MaterialMapManager {
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.
* Will return if the material is already the given type.
@ -24,19 +57,21 @@ export class MaterialMapManager {
switch (newMaterialType) {
case MaterialType.solid:
ASSERT(currentMaterial.type === MaterialType.textured, 'Old material expect to be texture');
this.materials.set(materialName, {
type: MaterialType.solid,
colour: RGBAColours.MAGENTA,
canBeTextured: currentMaterial.canBeTextured,
canBeTextured: true,
needsAttention: true,
});
break;
case MaterialType.textured:
ASSERT(currentMaterial.type === MaterialType.solid, 'Old material expect to be solid');
this.materials.set(materialName, {
type: MaterialType.textured,
alphaFactor: 1.0,
alphaPath: undefined,
canBeTextured: currentMaterial.canBeTextured,
transparency: {
type: 'None',
},
extension: 'repeat',
interpolation: 'linear',
needsAttention: true,

View File

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

View File

@ -7,7 +7,7 @@ import { DebugGeometryTemplates } from './geometry';
import { MaterialType, SolidMaterial, TexturedMaterial } from './mesh';
import { RenderBuffer } from './render_buffer';
import { ShaderManager } from './shaders';
import { Texture } from './texture';
import { EImageChannel, Texture } from './texture';
import { ASSERT } from './util/error_util';
import { Vector3 } from './vector';
import { RenderMeshParams, RenderNextBlockMeshChunkParams, RenderNextVoxelMeshChunkParams } from './worker_types';
@ -30,10 +30,28 @@ enum EDebugBufferComponents {
}
/* 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 {
public _gl: WebGLRenderingContext;
@ -48,7 +66,7 @@ export class Renderer {
private _modelsAvailable: number;
private _materialBuffers: Map<string, {
material: SolidMaterial | (TexturedMaterial & TextureMaterialRenderAddons)
material: InternalSolidMaterial | InternalTextureMaterial,
buffer: twgl.BufferInfo,
numElements: number,
materialName: string,
@ -192,123 +210,77 @@ export class Renderer {
this.setModelToUse(MeshType.None);
}
public recreateMaterialBuffer(materialName: string, material: SolidMaterial | TexturedMaterial) {
const oldBuffer = this._materialBuffers.get(materialName);
ASSERT(oldBuffer !== undefined);
private _createInternalMaterial(material: SolidMaterial | TexturedMaterial): (InternalSolidMaterial | InternalTextureMaterial) {
if (material.type === MaterialType.solid) {
this._materialBuffers.set(materialName, {
buffer: oldBuffer.buffer,
material: {
type: MaterialType.solid,
colour: RGBAUtil.copy(material.colour),
needsAttention: material.needsAttention,
canBeTextured: material.canBeTextured,
},
numElements: oldBuffer.numElements,
materialName: materialName,
});
return {
type: MaterialType.solid,
colourArray: RGBAUtil.toArray(material.colour),
};
} else {
this._materialBuffers.set(materialName, {
buffer: oldBuffer.buffer,
material: {
type: MaterialType.textured,
path: material.path,
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 diffuseTexture = 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,
});
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) {
this._materialBuffers.forEach((buffer) => {
if (buffer.materialName === materialName) {
buffer.material = {
type: MaterialType.textured,
path: material.path,
interpolation: material.interpolation,
extension: material.extension,
canBeTextured: material.canBeTextured,
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,
};
return;
}
public recreateMaterialBuffer(materialName: string, material: SolidMaterial | TexturedMaterial) {
const oldBuffer = this._materialBuffers.get(materialName);
ASSERT(oldBuffer !== undefined);
const internalMaterial = this._createInternalMaterial(material);
this._materialBuffers.set(materialName, {
buffer: oldBuffer.buffer,
material: internalMaterial,
numElements: oldBuffer.numElements,
materialName: materialName,
});
}
public useMesh(params: RenderMeshParams.Output) {
this._materialBuffers = new Map();
for (const { material, buffer, numElements, materialName } of params.buffers) {
if (material.type === MaterialType.solid) {
this._materialBuffers.set(materialName, {
buffer: twgl.createBufferInfoFromArrays(this._gl, buffer),
material: material,
numElements: numElements,
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,
});
}
const internalMaterial = this._createInternalMaterial(material);
this._materialBuffers.set(materialName, {
buffer: twgl.createBufferInfoFromArrays(this._gl, buffer),
material: internalMaterial,
numElements: numElements,
materialName: materialName,
});
}
this._gridBuffers.x[MeshType.TriangleMesh] = DebugGeometryTemplates.gridX(params.dimensions);
@ -458,9 +430,6 @@ export class Renderer {
}
}
public parseRawMeshData(buffer: string, dimensions: Vector3) {
}
private _drawMesh() {
this._materialBuffers.forEach((materialBuffer, materialName) => {
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_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
u_worldInverseTranspose: ArcballCamera.Get.getWorldInverseTranspose(),
u_texture: materialBuffer.material.texture,
u_alpha: materialBuffer.material.alpha || materialBuffer.material.texture,
u_useAlphaMap: materialBuffer.material.alpha !== undefined,
u_useAlphaChannel: materialBuffer.material.useAlphaChannel,
u_alphaFactor: materialBuffer.material.alphaFactor,
u_texture: materialBuffer.material.diffuseTexture,
u_alpha: materialBuffer.material.alphaTexture ?? materialBuffer.material.diffuseTexture,
u_alphaChannel: materialBuffer.material.alphaChannel,
u_alphaFactor: materialBuffer.material.alphaValue,
u_cameraDir: ArcballCamera.Get.getCameraDirection().toArray(),
u_fresnelExponent: AppConfig.Get.FRESNEL_EXPONENT,
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_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
u_worldInverseTranspose: ArcballCamera.Get.getWorldInverseTranspose(),
u_fillColour: RGBAUtil.toArray(materialBuffer.material.colour),
u_fillColour: materialBuffer.material.colourArray,
u_cameraDir: ArcballCamera.Get.getCameraDirection().toArray(),
u_fresnelExponent: AppConfig.Get.FRESNEL_EXPONENT,
u_fresnelMix: AppConfig.Get.FRESNEL_MIX,

View File

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

View File

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

View File

@ -1,5 +1,7 @@
import { MaterialType, TexturedMaterial } from '../../mesh';
import { EImageChannel, TTransparencyTypes } from '../../texture';
import { getRandomID } from '../../util';
import { ASSERT } from '../../util/error_util';
import { ComboBoxElement } from './combobox';
import { ConfigUIElement } from './config_element';
import { ImageElement } from './image_element';
@ -11,9 +13,12 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
private _colourId: string;
private _filteringElement: ComboBoxElement<'nearest' | 'linear'>;
private _wrapElement: ComboBoxElement<'clamp' | 'repeat'>;
private _transparencyElement: ComboBoxElement<TTransparencyTypes>;
private _imageElement: ImageElement;
private _typeElement: MaterialTypeElement;
private _alphaElement: SliderElement;
private _alphaValueElement?: SliderElement;
private _alphaMapElement?: ImageElement;
private _alphaChannelElement?: ComboBoxElement<EImageChannel>;
public constructor(materialName: string, material: TexturedMaterial) {
super(material);
@ -32,17 +37,39 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
.setSmall()
.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._typeElement = new MaterialTypeElement(MaterialType.textured);
this._alphaElement = new SliderElement()
.setMin(0.0)
.setMax(1.0)
.setDefaultValue(material.alphaFactor)
.setDecimals(2)
.setStep(0.01)
.setSmall();
switch (material.transparency.type) {
case 'UseAlphaValue':
this._alphaValueElement = new SliderElement()
.setMin(0.0)
.setMax(1.0)
.setDefaultValue(material.transparency.alpha)
.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 {
@ -50,7 +77,10 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._typeElement.registerEvents();
this._filteringElement.registerEvents();
this._wrapElement.registerEvents();
this._alphaElement.registerEvents();
this._transparencyElement.registerEvents();
this._alphaValueElement?.registerEvents();
this._alphaMapElement?.registerEvents();
this._alphaChannelElement?.registerEvents();
this._imageElement.addValueChangedListener((newPath) => {
const material = this.getValue();
@ -71,14 +101,30 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._onChangeTypeDelegate?.();
});
this._alphaElement.addValueChangedListener((newAlpha) => {
this.getValue().alphaFactor = newAlpha;
this._alphaValueElement?.addValueChangedListener((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 {
const material = this.getValue();
const subproperties: string[] = [];
const addSubproperty = (key: string, value: string) => {
subproperties.push(`
@ -94,10 +140,18 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
};
addSubproperty('Type', this._typeElement._generateInnerHTML());
addSubproperty('Alpha', this._alphaElement._generateInnerHTML());
addSubproperty('File', this._imageElement._generateInnerHTML());
addSubproperty('Diffuse map', this._imageElement._generateInnerHTML());
addSubproperty('Filtering', this._filteringElement._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 `
<div class="subproperty-container">
@ -120,6 +174,10 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._typeElement.finalise();
this._filteringElement.finalise();
this._wrapElement.finalise();
this._transparencyElement.finalise();
this._alphaValueElement?.finalise();
this._alphaMapElement?.finalise();
this._alphaChannelElement?.finalise();
}
private _onChangeTypeDelegate?: () => void;
@ -127,4 +185,10 @@ export class TexturedMaterialElement extends ConfigUIElement<TexturedMaterial, H
this._onChangeTypeDelegate = delegate;
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 fs from 'fs';
import path from 'path';
import { LOGF } from './log_util';
@ -27,7 +28,8 @@ export namespace FileUtil {
child.exec(`open -R ${absolutePath}`);
break;
case 'win32':
child.exec(`explorer /select,"${absolutePath}"`);
const parsed = path.parse(absolutePath);
child.exec(`start ${parsed.dir}`);
}
}
}