forked from mirror/ObjToSchematic
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:
parent
3b2d126264
commit
458b2ddf58
@ -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
BIN
res/static/debug_alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
33
src/mesh.ts
33
src/mesh.ts
@ -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 {
|
||||
|
202
src/renderer.ts
202
src/renderer.ts
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user