diff --git a/res/shaders/solid_tri_fragment.fs b/res/shaders/solid_tri_fragment.fs index 950ac45..5ddc87a 100644 --- a/res/shaders/solid_tri_fragment.fs +++ b/res/shaders/solid_tri_fragment.fs @@ -4,6 +4,88 @@ uniform vec4 u_fillColour; varying float v_lighting; +float dither8x8(vec2 position, float alpha) { + int x = int(mod(position.x, 8.0)); + int y = int(mod(position.y, 8.0)); + int index = x + y * 8; + float limit = 0.0; + + if (x < 8) { + if (index == 0) limit = 0.015625; + if (index == 1) limit = 0.515625; + if (index == 2) limit = 0.140625; + if (index == 3) limit = 0.640625; + if (index == 4) limit = 0.046875; + if (index == 5) limit = 0.546875; + if (index == 6) limit = 0.171875; + if (index == 7) limit = 0.671875; + if (index == 8) limit = 0.765625; + if (index == 9) limit = 0.265625; + if (index == 10) limit = 0.890625; + if (index == 11) limit = 0.390625; + if (index == 12) limit = 0.796875; + if (index == 13) limit = 0.296875; + if (index == 14) limit = 0.921875; + if (index == 15) limit = 0.421875; + if (index == 16) limit = 0.203125; + if (index == 17) limit = 0.703125; + if (index == 18) limit = 0.078125; + if (index == 19) limit = 0.578125; + if (index == 20) limit = 0.234375; + if (index == 21) limit = 0.734375; + if (index == 22) limit = 0.109375; + if (index == 23) limit = 0.609375; + if (index == 24) limit = 0.953125; + if (index == 25) limit = 0.453125; + if (index == 26) limit = 0.828125; + if (index == 27) limit = 0.328125; + if (index == 28) limit = 0.984375; + if (index == 29) limit = 0.484375; + if (index == 30) limit = 0.859375; + if (index == 31) limit = 0.359375; + if (index == 32) limit = 0.0625; + if (index == 33) limit = 0.5625; + if (index == 34) limit = 0.1875; + if (index == 35) limit = 0.6875; + if (index == 36) limit = 0.03125; + if (index == 37) limit = 0.53125; + if (index == 38) limit = 0.15625; + if (index == 39) limit = 0.65625; + if (index == 40) limit = 0.8125; + if (index == 41) limit = 0.3125; + if (index == 42) limit = 0.9375; + if (index == 43) limit = 0.4375; + if (index == 44) limit = 0.78125; + if (index == 45) limit = 0.28125; + if (index == 46) limit = 0.90625; + if (index == 47) limit = 0.40625; + if (index == 48) limit = 0.25; + if (index == 49) limit = 0.75; + if (index == 50) limit = 0.125; + if (index == 51) limit = 0.625; + if (index == 52) limit = 0.21875; + if (index == 53) limit = 0.71875; + if (index == 54) limit = 0.09375; + if (index == 55) limit = 0.59375; + if (index == 56) limit = 1.0; + if (index == 57) limit = 0.5; + if (index == 58) limit = 0.875; + if (index == 59) limit = 0.375; + if (index == 60) limit = 0.96875; + if (index == 61) limit = 0.46875; + if (index == 62) limit = 0.84375; + if (index == 63) limit = 0.34375; + } + + return alpha < limit ? 0.0 : 1.0; +} + void main() { + float alpha = dither8x8(gl_FragCoord.xy, u_fillColour.a); + if (alpha < 0.5) + { + discard; + } + gl_FragColor = vec4(u_fillColour.rgb * v_lighting, u_fillColour.a); } diff --git a/src/importers/obj_importer.ts b/src/importers/obj_importer.ts index 923ef38..30299de 100644 --- a/src/importers/obj_importer.ts +++ b/src/importers/obj_importer.ts @@ -403,7 +403,12 @@ export class ObjImporter extends IImporter { } else { this._materials[this._currentMaterialName] = { type: MaterialType.solid, - colour: this._currentColour, + colour: { + r: this._currentColour.r, + g: this._currentColour.g, + b: this._currentColour.b, + a: this._currentAlpha, + }, edited: false, canBeTextured: false, }; diff --git a/src/mesh.ts b/src/mesh.ts index 6ba4e96..e4ba9e2 100644 --- a/src/mesh.ts +++ b/src/mesh.ts @@ -150,7 +150,9 @@ export class Mesh { throw new AppError('Loaded mesh has no materials'); } + // Check used materials exist + const usedMaterials = new Set(); const missingMaterials = new Set(); for (const tri of this._tris) { if (!(tri.material in this._materials)) { @@ -178,7 +180,21 @@ export class Mesh { missingMaterials.add(tri.material); } + + usedMaterials.add(tri.material); } + + const materialsToRemove = new Set(); + for (const materialName in this._materials) { + if (!usedMaterials.has(materialName)) { + LOG_WARN(`'${materialName}' is not used by any triangles, removing...`); + materialsToRemove.add(materialName); + } + } + materialsToRemove.forEach((materialName) => { + delete this._materials[materialName]; + }); + if (missingMaterials.size > 0) { LOG_WARN('Triangles use these materials but they were not found', missingMaterials); } diff --git a/src/ui/elements/material.ts b/src/ui/elements/material.ts index ae5d6e2..79382e2 100644 --- a/src/ui/elements/material.ts +++ b/src/ui/elements/material.ts @@ -5,17 +5,20 @@ import { AppContext } from '../../app_context'; import { RGBAUtil } from '../../colour'; import { SolidMaterial, TexturedMaterial } from '../../mesh'; import { getRandomID } from '../../util'; +import { ASSERT } from '../../util/error_util'; import { FileUtil } from '../../util/file_util'; export abstract class MaterialUIElement { protected readonly _materialName: string; protected readonly _appContext: AppContext; private _actions: { text: string, onClick: () => void, id: string }[]; + private _metadata: string[]; public constructor(materialName: string, appContext: AppContext) { this._materialName = materialName; this._appContext = appContext; this._actions = []; + this._metadata = []; } public hasWarning() { @@ -23,10 +26,15 @@ export abstract class MaterialUIElement { } public buildHTML(): string { - let html = this.buildChildHTML(); + let html = `
`; + html += this.buildChildHTML(); + this._metadata.forEach((data) => { + html += `
${data}`; + }); this._actions.forEach((action) => { html += `
[${action.text}]`; }); + html += `
`; return html; } @@ -45,17 +53,23 @@ export abstract class MaterialUIElement { this._actions.push({ text: text, onClick: onClick, id: getRandomID() }); } + public addMetadata(text: string) { + this._metadata.push(text); + } + protected abstract buildChildHTML(): string } export class TextureMaterialUIElement extends MaterialUIElement { private _material: TexturedMaterial; - private _imageId: string; + private _diffuseImageId: string; + private _alphaImageId: string; public constructor(materialName: string, appContext: AppContext, material: TexturedMaterial) { super(materialName, appContext); this._material = material; - this._imageId = getRandomID(); + this._diffuseImageId = getRandomID(); + this._alphaImageId = getRandomID(); const parsedPath = path.parse(material.path); const isMissingTexture = parsedPath.base === 'debug.png'; @@ -77,6 +91,11 @@ export class TextureMaterialUIElement extends MaterialUIElement { super.addAction('Switch to colour', () => { this._appContext.onMaterialTypeSwitched(materialName); }); + + super.addMetadata(`Alpha multiplier: ${this._material.alphaFactor}`); + if (this._material.alphaPath !== undefined) { + super.addMetadata(`Alpha texture: ${this._material.alphaPath}`); + } } private _isMissingTexture() { @@ -90,26 +109,51 @@ export class TextureMaterialUIElement extends MaterialUIElement { } protected buildChildHTML(): string { - return ``; + let html = ``; + if (this._material.alphaPath !== undefined) { + html += `
`; + } + return html; } public registerEvents(): void { super.registerEvents(); - const element = document.getElementById(this._imageId) as HTMLLinkElement; - if (element) { - if (!this._isMissingTexture()) { - element.addEventListener('mouseover', () => { - element.classList.add('texture-hover'); - }); - element.addEventListener('mouseleave', () => { - element.classList.remove('texture-hover'); - }); - element.addEventListener('click', () => { - FileUtil.openDir(this._material.path); - }); - } else { - element.classList.add('texture-preview-missing'); + { + const element = document.getElementById(this._diffuseImageId) as HTMLLinkElement; + if (element) { + if (!this._isMissingTexture()) { + element.addEventListener('mouseover', () => { + element.classList.add('texture-hover'); + }); + element.addEventListener('mouseleave', () => { + element.classList.remove('texture-hover'); + }); + element.addEventListener('click', () => { + FileUtil.openDir(this._material.path); + }); + } else { + element.classList.add('texture-preview-missing'); + } + } + } + { + const element = document.getElementById(this._alphaImageId) as HTMLLinkElement; + if (element) { + if (!this._isMissingTexture()) { + element.addEventListener('mouseover', () => { + element.classList.add('texture-hover'); + }); + element.addEventListener('mouseleave', () => { + element.classList.remove('texture-hover'); + }); + element.addEventListener('click', () => { + ASSERT(this._material.alphaPath !== undefined); + FileUtil.openDir(this._material.alphaPath); + }); + } else { + element.classList.add('texture-preview-missing'); + } } } } @@ -129,6 +173,8 @@ export class SolidMaterialUIElement extends MaterialUIElement { this._appContext.onMaterialTypeSwitched(materialName); }); } + + this.addMetadata(`Alpha multiplier: ${this._material.colour.a}`); } protected buildChildHTML(): string { @@ -142,6 +188,7 @@ export class SolidMaterialUIElement extends MaterialUIElement { if (colourElement !== null) { colourElement.addEventListener('change', () => { const newColour = RGBAUtil.fromHexString(colourElement.value); + newColour.a = this._material.colour.a; this._appContext.onMaterialColourChanged(this._materialName, newColour); }); diff --git a/src/ui/misc.ts b/src/ui/misc.ts index f4f74b4..0b2e67b 100644 --- a/src/ui/misc.ts +++ b/src/ui/misc.ts @@ -75,7 +75,7 @@ export class UITreeBuilder implements IUIOutputElement { } else { childrenHTML += child.warning ? `

${child.html}

` : child.html; } - childrenHTML += '
  • '; + childrenHTML += '
  • '; }); if (this.getWarning()) { diff --git a/styles.css b/styles.css index 1129ff2..be44095 100644 --- a/styles.css +++ b/styles.css @@ -696,4 +696,9 @@ a:hover { .texture-hover { border-color: var(--text-standard) !important; +} + +.material-container { + border-left: 1px solid #8C8C8C80; + padding-left: 5px; } \ No newline at end of file