From e4bec861c04c71bc374f58dfdae13d9ab49c492b Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Sun, 16 Jan 2022 02:25:28 +0000 Subject: [PATCH] Action output message, more options to toggle --- .gitignore | 3 +- src/app_context.ts | 108 ++++++++++++++++++++++++++++++-------- src/ui/elements/button.ts | 4 +- src/ui/elements/output.ts | 32 +++++++++++ src/ui/layout.ts | 16 +++--- src/voxel_manager.ts | 24 +++++++-- styles.css | 18 +++++-- 7 files changed, 167 insertions(+), 38 deletions(-) create mode 100644 src/ui/elements/output.ts diff --git a/.gitignore b/.gitignore index bfafd24..e6bbea5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ ObjToSchematic-win32-x64 /resources/*.mtl /dist /tools/blocks -/tools/models \ No newline at end of file +/tools/models +notes.md \ No newline at end of file diff --git a/src/app_context.ts b/src/app_context.ts index fdb62de..1ece146 100644 --- a/src/app_context.ts +++ b/src/app_context.ts @@ -12,19 +12,35 @@ import { remote } from "electron"; import fs from "fs"; import { ButtonElement } from "./ui/elements/button"; import { LabelElement } from "./ui/elements/label"; +import { OutputElement } from "./ui/elements/output"; +export enum ActionReturnType { + Success, + Warning, + Failure +} interface ReturnStatus { message: string, - //style: ToastStyle, + type: ActionReturnType, error?: unknown } +export enum Action { + Load = 0, + Simplify = 1, + Voxelise = 2, + Palette = 3, + Export = 4, +} + export class AppContext { public ambientOcclusion: boolean; + public dithering: boolean; private _gl: WebGLRenderingContext; private _loadedMesh?: Mesh; private _ui: Group[]; + private _actionMap = new Map ReturnStatus | void)>(); private static _instance: AppContext; @@ -34,6 +50,13 @@ export class AppContext { private constructor() { this.ambientOcclusion = true; + this.dithering = true; + + this._actionMap.set(Action.Load, () => { return this._load(); }); + this._actionMap.set(Action.Simplify, () => {}); + this._actionMap.set(Action.Voxelise, () => { return this._voxelise(); }); + this._actionMap.set(Action.Palette, () => { return this._palette(); } ); + this._actionMap.set(Action.Export, () => { return this._export(); }); this._ui = [ { @@ -48,7 +71,8 @@ export class AppContext { type: new FileInputElement("mtlFile", "mtl") }, ], - submitButton: new ButtonElement("loadMesh", "Load mesh", () => { this._load(); }) + submitButton: new ButtonElement("loadMesh", "Load mesh", () => { this.do(Action.Load); }), + output: new OutputElement("output1") }, { label: "Simplify", @@ -57,7 +81,8 @@ export class AppContext { label: new LabelElement("label3", "Ratio"), type: new SliderElement("ratio", 0.0, 1.0, 0.01, 0.5) }, ], - submitButton: new ButtonElement("simplifyMesh", "Simplify mesh", () => { }) + submitButton: new ButtonElement("simplifyMesh", "Simplify mesh", () => { }), + output: new OutputElement("output2") }, { label: "Build", @@ -74,25 +99,34 @@ export class AppContext { ]) }, ], - submitButton: new ButtonElement("voxeliseMesh", "Voxelise mesh", () => { this._voxelise(); }) + submitButton: new ButtonElement("voxeliseMesh", "Voxelise mesh", () => { this.do(Action.Voxelise); }), + output: new OutputElement("output3") }, { label: "Palette", components: [ { label: new LabelElement("label6", "Block palette"), - type: new ComboBoxElement("blockPalette", []) + type: new ComboBoxElement("blockPalette", [ + { id: "default", displayText: "Default" } + ]) }, { label: new LabelElement("label7", "Choice method"), - type: new ComboBoxElement("choiceMethod", []) + type: new ComboBoxElement("choiceMethod", [ + { id: "euclidian", displayText: "Euclidian distance" } + ]) }, { label: new LabelElement("label8", "Dithering"), - type: new ComboBoxElement("dithering", []) + type: new ComboBoxElement("dithering", [ + { id: "on", displayText: "On (recommended)" }, + { id: "off", displayText: "Off" }, + ]) }, ], - submitButton: new ButtonElement("assignBlocks", "Assign blocks", () => { this._export(); }) + submitButton: new ButtonElement("assignBlocks", "Assign blocks", () => { this.do(Action.Palette); }), + output: new OutputElement("output4") }, { label: "Export", @@ -106,7 +140,8 @@ export class AppContext { ]) }, ], - submitButton: new ButtonElement("exportStructure", "Export structure", () => { this._export(); }) + submitButton: new ButtonElement("exportStructure", "Export structure", () => { this.do(Action.Export); }), + output: new OutputElement("output5") } ] @@ -124,25 +159,35 @@ export class AppContext { this._gl = gl; } + public do(action: Action) { + const status = this._actionMap.get(action)!(); + if (status) { + this._ui[action].output.setMessage(status.message, status.type); + if (status.error) { + console.error(status.error); + } + } + } + private _load(): ReturnStatus { setEnabled(this._ui[2], false); setEnabled(this._ui[4], false); const objPath = (this._ui[0].components[0].type).getValue(); if (!fs.existsSync(objPath)) { - return { message: "Selected .obj cannot be found" }; + return { message: "Selected .obj cannot be found", type: ActionReturnType.Failure }; } const mtlPath = (this._ui[0].components[1].type).getValue(); if (!fs.existsSync(mtlPath)) { - return { message: "Selected .mtl cannot be found" }; + return { message: "Selected .mtl cannot be found", type: ActionReturnType.Failure }; } try { this._loadedMesh = new Mesh(objPath, this._gl); this._loadedMesh.loadTextures(this._gl); } catch (err: unknown) { - return { error: err, message: "Could not load mesh" }; + return { error: err, message: "Could not load mesh", type: ActionReturnType.Failure }; } try { @@ -151,19 +196,19 @@ export class AppContext { renderer.registerMesh(this._loadedMesh); renderer.compile(); } catch (err: unknown) { - return { message: "Could not render mesh" }; + return { message: "Could not render mesh", type: ActionReturnType.Failure }; } setEnabled(this._ui[2], true); - return { message: "Loaded successfully" }; + return { message: "Loaded successfully", type: ActionReturnType.Success }; } private _voxelise(): ReturnStatus { + setEnabled(this._ui[3], false); setEnabled(this._ui[4], false); const voxelSize = (this._ui[2].components[0].type).getValue(); const ambientOcclusion = (this._ui[2].components[1].type).getValue(); - this.ambientOcclusion = ambientOcclusion === "on"; try { @@ -176,15 +221,36 @@ export class AppContext { renderer.registerVoxelMesh(); renderer.compile(); } catch (err: any) { - return { error: err, message: "Could not register voxel mesh" };//, style: ToastStyle.Failure }; + return { error: err, message: "Could not register voxel mesh", type: ActionReturnType.Failure }; + } + + setEnabled(this._ui[3], true); + return { message: "Voxelised successfully", type: ActionReturnType.Success }; + } + + private _palette(): ReturnStatus { + setEnabled(this._ui[4], false); + + const dithering = (this._ui[3].components[2].type).getValue(); + this.dithering = dithering === "on"; + + try { + const voxelManager = VoxelManager.Get; + voxelManager.assignBlocks(); + + const renderer = Renderer.Get; + renderer.clear(); + renderer.registerVoxelMesh(); + renderer.compile(); + } catch (err: any) { + return { error: err, message: "Could not register voxel mesh", type: ActionReturnType.Failure }; } setEnabled(this._ui[4], true); - return { message: "Voxelised successfully" };//, style: ToastStyle.Success }; + return { message: "Blocks assigned successfully", type: ActionReturnType.Success }; } private _export(): ReturnStatus { - console.log(this._ui[4].components[0]); const exportFormat = (this._ui[4].components[0].type).getValue(); let exporter: Exporter; if (exportFormat === "schematic") { @@ -200,16 +266,16 @@ export class AppContext { }); if (filePath === undefined) { - return { message: "Output cancelled" };//, style: ToastStyle.Success }; + return { message: "Output cancelled", type: ActionReturnType.Warning }; } try { exporter.export(filePath); } catch (err: unknown) { - return { error: err, message: "Failed to export" };//, style: ToastStyle.Failure }; + return { error: err, message: "Failed to export", type: ActionReturnType.Failure }; } - return { message: "Successfully exported" };//, style: ToastStyle.Success }; + return { message: "Successfully exported", type: ActionReturnType.Success }; } public draw() { diff --git a/src/ui/elements/button.ts b/src/ui/elements/button.ts index 0ec2f3f..46c9c10 100644 --- a/src/ui/elements/button.ts +++ b/src/ui/elements/button.ts @@ -3,7 +3,7 @@ import { assert } from "../../util"; export class ButtonElement extends BaseUIElement { private _label: string; - private _onClick: () => void + private _onClick: () => void; public constructor(id: string, label: string, onClick: () => void) { super(id); @@ -26,7 +26,7 @@ export class ButtonElement extends BaseUIElement { element.addEventListener("click", () => { if (this._isEnabled) { - this._onClick() + this._onClick(); } }); } diff --git a/src/ui/elements/output.ts b/src/ui/elements/output.ts new file mode 100644 index 0000000..1dc2fbb --- /dev/null +++ b/src/ui/elements/output.ts @@ -0,0 +1,32 @@ +import { BaseUIElement } from "../layout"; +import { assert } from "../../util"; +import { ActionReturnType } from "../../app_context"; + +export class OutputElement { + private _id: string; + + public constructor(id: string) { + this._id = id; + } + + public generateHTML() { + return ` +
+
+ `; + } + + public setMessage(message: string, returnType: ActionReturnType) { + const element = document.getElementById(this._id) as HTMLDivElement; + assert(element !== null); + + element.innerHTML = message; + element.classList.remove("border-warning"); + element.classList.remove("border-failure"); + if (returnType === ActionReturnType.Warning) { + element.classList.add("border-warning"); + } else if (returnType === ActionReturnType.Failure) { + element.classList.add("border-error"); + } + } +} \ No newline at end of file diff --git a/src/ui/layout.ts b/src/ui/layout.ts index 9d2d07d..3a8214d 100644 --- a/src/ui/layout.ts +++ b/src/ui/layout.ts @@ -1,15 +1,17 @@ import { ButtonElement } from "./elements/button"; import { LabelElement } from "./elements/label"; +import { OutputElement } from "./elements/output"; export interface Group { - label: string, - components: Component[] - submitButton: ButtonElement + label: string; + components: Component[]; + submitButton: ButtonElement; + output: OutputElement; } export interface Component { - label : LabelElement, - type: BaseUIElement, + label: LabelElement; + type: BaseUIElement; } export abstract class BaseUIElement { @@ -59,9 +61,7 @@ function buildComponent(componentParams: Group) {
-
- Nothing -
+ ${componentParams.output.generateHTML()}
`; diff --git a/src/voxel_manager.ts b/src/voxel_manager.ts index 98fd5b9..79ef32e 100644 --- a/src/voxel_manager.ts +++ b/src/voxel_manager.ts @@ -8,7 +8,7 @@ import { Mesh, MaterialType } from "./mesh"; import { triangleArea } from "./math"; import { Axes, generateRays, rayIntersectTriangle } from "./ray"; import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from "./block_assigner.js"; -import { AppConfig } from "./config.js"; +import { AppContext } from "./app_context.js"; interface Block { position: Vector3; @@ -77,6 +77,7 @@ export class VoxelManager { public assignBlocks() { this.blockPalette = []; + this.voxelTexcoords = []; let meanSquaredError = 0.0; for (let i = 0; i < this.voxels.length; ++i) { @@ -84,7 +85,8 @@ export class VoxelManager { const averageColour = getAverageColour(voxel.colours!); - const blockAssigner = AppConfig.DITHERING_ENABLED ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner(); + const ditheringEnabled = AppContext.Get.dithering; + const blockAssigner = ditheringEnabled ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner(); const block = blockAssigner.assignBlock(averageColour, voxel.position); const squaredError = Math.pow(255 * (block.colour.r - averageColour.r), 2) + Math.pow(255 * (block.colour.g - averageColour.g), 2) + Math.pow(255 * (block.colour.b - averageColour.b), 2); @@ -97,6 +99,17 @@ export class VoxelManager { console.log("Mean Squared Error:", meanSquaredError); } + public assignBlankBlocks() { + this.blockPalette = []; + + const whiteColour = { r: 1.0, g: 1.0, b: 1.0 }; + const block = new BasicBlockAssigner().assignBlock(whiteColour, new Vector3(0, 0, 0)); + + for (let i = 0; i < this.voxels.length; ++i) { + this._assignBlock(i, block); + } + } + public addVoxel(pos: Vector3, block: BlockInfo) { // Is there already a voxel in this position? let voxel = this.voxelsHash.get(pos); @@ -176,6 +189,10 @@ export class VoxelManager { }); } + public paintMesh() { + this.assignBlocks(); + } + voxeliseMesh(mesh: Mesh) { this._clearVoxels(); @@ -194,7 +211,8 @@ export class VoxelManager { }); }); - this.assignBlocks(); + this.assignBlankBlocks(); + //this.assignBlocks(); } } \ No newline at end of file diff --git a/styles.css b/styles.css index 5d6e26b..9ad68ec 100644 --- a/styles.css +++ b/styles.css @@ -5,6 +5,7 @@ --canvas-width: calc(100% - var(--properties-width)); --pill-radius: 6px; --prim-color: #008C74; + --prim-color-hover: #009e84; --prim-color-disabled: #004e41; --prop-bg-color: #333333; } @@ -116,7 +117,7 @@ canvas { font-size: 90%; padding: 12px 18px; line-height: 180%; - border: 1px solid rgb(255, 255, 255, 0); + min-height: 22px; } .round-top { @@ -147,11 +148,14 @@ select { color: #A8A8A8; background-color: #2F2F2F; } - select:hover { color: #C6C6C6; background-color: #383838; } +select:disabled { + background-color: #282828 !important; + color: #535353; +} .file-input { border-radius: 5px; @@ -209,7 +213,7 @@ select:hover { } .button:hover { border: 1px solid rgb(255, 255, 255, 0.2); - background-color: #00A6D8; + background-color: var(--prim-color-hover); cursor: pointer; } .button-disabled { @@ -273,4 +277,12 @@ select:hover { ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.2); +} + +.border-warning { + border: 1.5px solid orange; +} + +.border-error { + border: 1.5px solid red; } \ No newline at end of file