mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2025-03-07 14:06:41 +08:00
Major refactor to app_context.ts
This commit is contained in:
parent
4144497cd5
commit
81c7d6d28b
3
src/actions/action_import.ts
Normal file
3
src/actions/action_import.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class ActionImport {
|
||||
|
||||
}
|
@ -4,22 +4,17 @@ import { FallableBehaviour } from './block_mesh';
|
||||
import { ArcballCamera } from './camera';
|
||||
import { AppConfig } from './config';
|
||||
import { EAppEvent, EventManager } from './event';
|
||||
import { TExporters } from './exporters/exporters';
|
||||
import { MaterialMapManager } from './material-map';
|
||||
import { MaterialType } from './mesh';
|
||||
import { MeshType, Renderer } from './renderer';
|
||||
import { PlaceholderComponent } from './ui/components/placeholder';
|
||||
import { SolidMaterialComponent } from './ui/components/solid_material';
|
||||
import { TexturedMaterialComponent } from './ui/components/textured_material';
|
||||
import { AppConsole, TMessage } from './ui/console';
|
||||
import { UI } from './ui/layout';
|
||||
import { ColourSpace, EAction } from './util';
|
||||
import { ASSERT } from './util/error_util';
|
||||
import { download } from './util/file_util';
|
||||
import { Logger } from './util/log_util';
|
||||
import { LOG_ERROR, Logger } from './util/log_util';
|
||||
import { Vector3 } from './vector';
|
||||
import { TWorkerJob, WorkerController } from './worker_controller';
|
||||
import { TFromWorkerMessage, TToWorkerMessage } from './worker_types';
|
||||
import { WorkerController } from './worker_controller';
|
||||
import { TFromWorkerMessage } from './worker_types';
|
||||
|
||||
export class AppContext {
|
||||
private _ui: UI;
|
||||
@ -37,9 +32,6 @@ export class AppContext {
|
||||
|
||||
AppConfig.Get.dumpConfig();
|
||||
|
||||
// TODO Unimplemented
|
||||
//FileUtil.rmdirIfExist(AppPaths.Get.gen);
|
||||
|
||||
const gl = (<HTMLCanvasElement>document.getElementById('canvas')).getContext('webgl');
|
||||
if (!gl) {
|
||||
throw Error('Could not load WebGL context');
|
||||
@ -48,11 +40,14 @@ export class AppContext {
|
||||
this._ui = new UI(this);
|
||||
this._ui.build();
|
||||
this._ui.registerEvents();
|
||||
this._ui.disable(EAction.Materials);
|
||||
this._updateMaterialsAction();
|
||||
//this._ui.disable(EAction.Materials);
|
||||
this._ui.updateMaterialsAction(this._materialManager);
|
||||
this._ui.disableAll();
|
||||
|
||||
this._workerController = new WorkerController();
|
||||
this._workerController.addJob({ id: 'init', payload: { action: 'Init', params: {} } });
|
||||
this._workerController.execute({ action: 'Init', params: {}}).then(() => {
|
||||
this._ui.enable(EAction.Import);
|
||||
});
|
||||
|
||||
Renderer.Get.toggleIsAxesEnabled();
|
||||
ArcballCamera.Get.setCameraMode('perspective');
|
||||
@ -84,47 +79,268 @@ export class AppContext {
|
||||
AppConsole.info('Ready');
|
||||
}
|
||||
|
||||
public async do(action: EAction) {
|
||||
this._ui.cacheValues(action);
|
||||
this._ui.disable(action);
|
||||
this._ui.disableAll();
|
||||
private async _import(): Promise<boolean> {
|
||||
// Gather data from the UI to send to the worker
|
||||
const components = this._ui.layout.import.components;
|
||||
|
||||
const workerJob = await this._getWorkerJob(action);
|
||||
if (workerJob === undefined) {
|
||||
this._ui.enableTo(action);
|
||||
return;
|
||||
AppConsole.info('Importing mesh...');
|
||||
{
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultImport = await this._workerController.execute({
|
||||
action: 'Import',
|
||||
params: {
|
||||
file: components.input.getValue(),
|
||||
rotation: components.rotation.getValue(),
|
||||
},
|
||||
});
|
||||
|
||||
this._ui.getActionButton(EAction.Import).resetLoading();
|
||||
if (this._handleErrors(resultImport)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(resultImport.action === 'Import');
|
||||
|
||||
AppConsole.success('Imported mesh');
|
||||
this._addWorkerMessagesToConsole(resultImport.messages);
|
||||
|
||||
this.maxConstraint = Vector3.copy(resultImport.result.dimensions)
|
||||
.mulScalar(AppConfig.Get.CONSTRAINT_MAXIMUM_HEIGHT / 8.0).floor();
|
||||
this._materialManager = new MaterialMapManager(resultImport.result.materials);
|
||||
this._ui.updateMaterialsAction(this._materialManager);
|
||||
}
|
||||
|
||||
const jobCallback = (payload: TFromWorkerMessage) => {
|
||||
switch (payload.action) {
|
||||
case 'KnownError':
|
||||
case 'UnknownError': {
|
||||
AppConsole.error(payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong');
|
||||
this._ui.getActionButton(action)
|
||||
.stopLoading()
|
||||
.setProgress(0.0);
|
||||
AppConsole.info('Rendering mesh...');
|
||||
{
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultRender = await this._workerController.execute({
|
||||
action: 'RenderMesh',
|
||||
params: {},
|
||||
});
|
||||
|
||||
this._ui.enableTo(action);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ASSERT(payload.action !== 'Progress');
|
||||
this._addWorkerMessagesToConsole(payload.messages);
|
||||
|
||||
if (workerJob.callback) {
|
||||
workerJob.callback(payload);
|
||||
}
|
||||
}
|
||||
this._ui.getActionButton(EAction.Import).resetLoading();
|
||||
if (this._handleErrors(resultRender)) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
ASSERT(resultRender.action === 'RenderMesh');
|
||||
|
||||
this._addWorkerMessagesToConsole(resultRender.messages);
|
||||
Renderer.Get.useMesh(resultRender.result);
|
||||
}
|
||||
AppConsole.success('Rendered mesh');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private async _materials(): Promise<boolean> {
|
||||
AppConsole.info('Updating materials...');
|
||||
{
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultMaterials = await this._workerController.execute({
|
||||
action: 'SetMaterials',
|
||||
params: {
|
||||
materials: this._materialManager.materials,
|
||||
},
|
||||
});
|
||||
|
||||
this._ui.getActionButton(EAction.Materials).resetLoading();
|
||||
if (this._handleErrors(resultMaterials)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(resultMaterials.action === 'SetMaterials');
|
||||
|
||||
resultMaterials.result.materialsChanged.forEach((materialName) => {
|
||||
const material = this._materialManager.materials.get(materialName);
|
||||
ASSERT(material !== undefined);
|
||||
Renderer.Get.recreateMaterialBuffer(materialName, material);
|
||||
Renderer.Get.setModelToUse(MeshType.TriangleMesh);
|
||||
});
|
||||
|
||||
this._addWorkerMessagesToConsole(resultMaterials.messages);
|
||||
}
|
||||
AppConsole.success('Updated materials');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _voxelise(): Promise<boolean> {
|
||||
// Gather data from the UI to send to the worker
|
||||
const components = this._ui.layout.voxelise.components;
|
||||
|
||||
AppConsole.info('Loading voxel mesh...');
|
||||
{
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultVoxelise = await this._workerController.execute({
|
||||
action: 'Voxelise',
|
||||
params: {
|
||||
constraintAxis: components.constraintAxis.getValue(),
|
||||
voxeliser: components.voxeliser.getValue(),
|
||||
size: components.size.getValue(),
|
||||
useMultisampleColouring: components.multisampleColouring.getValue(),
|
||||
enableAmbientOcclusion: components.ambientOcclusion.getValue(),
|
||||
voxelOverlapRule: components.voxelOverlapRule.getValue(),
|
||||
},
|
||||
});
|
||||
|
||||
this._ui.getActionButton(EAction.Voxelise).resetLoading();
|
||||
if (this._handleErrors(resultVoxelise)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(resultVoxelise.action === 'Voxelise');
|
||||
|
||||
this._addWorkerMessagesToConsole(resultVoxelise.messages);
|
||||
}
|
||||
AppConsole.success('Loaded voxel mesh');
|
||||
|
||||
AppConsole.info('Rendering voxel mesh...');
|
||||
{
|
||||
let moreVoxelsToBuffer = false;
|
||||
do {
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultRender = await this._workerController.execute({
|
||||
action: 'RenderNextVoxelMeshChunk',
|
||||
params: {
|
||||
enableAmbientOcclusion: components.ambientOcclusion.getValue(),
|
||||
desiredHeight: components.size.getValue(),
|
||||
},
|
||||
});
|
||||
|
||||
this._ui.getActionButton(EAction.Voxelise).resetLoading();
|
||||
if (this._handleErrors(resultRender)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(resultRender.action === 'RenderNextVoxelMeshChunk');
|
||||
|
||||
moreVoxelsToBuffer = resultRender.result.moreVoxelsToBuffer;
|
||||
this._addWorkerMessagesToConsole(resultRender.messages);
|
||||
|
||||
Renderer.Get.useVoxelMeshChunk(resultRender.result);
|
||||
} while (moreVoxelsToBuffer);
|
||||
}
|
||||
AppConsole.success('Rendered voxel mesh');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _assign(): Promise<boolean> {
|
||||
// Gather data from the UI to send to the worker
|
||||
const components = this._ui.layout.assign.components;
|
||||
|
||||
AppConsole.info('Loading block mesh...');
|
||||
{
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultAssign = await this._workerController.execute({
|
||||
action: 'Assign',
|
||||
params: {
|
||||
textureAtlas: components.textureAtlas.getValue(),
|
||||
blockPalette: components.blockPalette.getValue().getBlocks(),
|
||||
dithering: components.dithering.getValue(),
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: components.fallable.getValue() as FallableBehaviour,
|
||||
resolution: Math.pow(2, components.colourAccuracy.getValue()),
|
||||
calculateLighting: components.calculateLighting.getValue(),
|
||||
lightThreshold: components.lightThreshold.getValue(),
|
||||
contextualAveraging: components.contextualAveraging.getValue(),
|
||||
errorWeight: components.errorWeight.getValue() / 10,
|
||||
},
|
||||
});
|
||||
|
||||
this._ui.getActionButton(EAction.Assign).resetLoading();
|
||||
if (this._handleErrors(resultAssign)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(resultAssign.action === 'Assign');
|
||||
|
||||
this._addWorkerMessagesToConsole(resultAssign.messages);
|
||||
}
|
||||
AppConsole.success('Loaded block mesh');
|
||||
|
||||
AppConsole.info('Rendering block mesh...');
|
||||
{
|
||||
let moreBlocksToBuffer = false;
|
||||
do {
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultRender = await this._workerController.execute({
|
||||
action: 'RenderNextBlockMeshChunk',
|
||||
params: {
|
||||
textureAtlas: components.textureAtlas.getValue(),
|
||||
},
|
||||
});
|
||||
|
||||
this._ui.getActionButton(EAction.Assign).resetLoading();
|
||||
if (this._handleErrors(resultRender)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(resultRender.action === 'RenderNextBlockMeshChunk');
|
||||
|
||||
moreBlocksToBuffer = resultRender.result.moreBlocksToBuffer;
|
||||
this._addWorkerMessagesToConsole(resultRender.messages);
|
||||
|
||||
Renderer.Get.useBlockMeshChunk(resultRender.result);
|
||||
} while (moreBlocksToBuffer);
|
||||
}
|
||||
AppConsole.success('Rendered voxel mesh');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _export(): Promise<boolean> {
|
||||
// Gather data from the UI to send to the worker
|
||||
const components = this._ui.layout.export.components;
|
||||
|
||||
AppConsole.info('Exporting structure...');
|
||||
{
|
||||
// Instruct the worker to perform the job and await the result
|
||||
const resultExport = await this._workerController.execute({
|
||||
action: 'Export',
|
||||
params: {
|
||||
filepath: '',
|
||||
exporter: components.export.getValue(),
|
||||
},
|
||||
});
|
||||
|
||||
this._ui.getActionButton(EAction.Export).resetLoading();
|
||||
if (this._handleErrors(resultExport)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(resultExport.action === 'Export');
|
||||
|
||||
this._addWorkerMessagesToConsole(resultExport.messages);
|
||||
download(resultExport.result.buffer, 'result.' + resultExport.result.extension);
|
||||
}
|
||||
AppConsole.success('Exported structure');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the result from the worker is an error message
|
||||
* if so, handle it and return true, otherwise false.
|
||||
*/
|
||||
private _handleErrors(result: TFromWorkerMessage) {
|
||||
if (result.action === 'KnownError') {
|
||||
AppConsole.error(result.error.message);
|
||||
return true;
|
||||
} else if (result.action === 'UnknownError') {
|
||||
AppConsole.error('Something unexpectedly went wrong');
|
||||
LOG_ERROR(result.error);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async do(action: EAction) {
|
||||
// Disable the UI while the worker is working
|
||||
this._ui.disableAll();
|
||||
|
||||
this._lastAction = action;
|
||||
|
||||
this._workerController.addJob({
|
||||
id: workerJob.id,
|
||||
payload: workerJob.payload,
|
||||
callback: jobCallback,
|
||||
});
|
||||
|
||||
const success = await this._executeAction(action);
|
||||
if (success) {
|
||||
this._ui.enableTo(action + 1);
|
||||
} else {
|
||||
this._ui.enableTo(action);
|
||||
}
|
||||
}
|
||||
|
||||
private _addWorkerMessagesToConsole(messages: TMessage[]) {
|
||||
@ -133,352 +349,22 @@ export class AppContext {
|
||||
});
|
||||
}
|
||||
|
||||
private _getWorkerJob(action: EAction): (Promise<TWorkerJob | undefined>) {
|
||||
private async _executeAction(action: EAction): Promise<boolean> {
|
||||
switch (action) {
|
||||
case EAction.Import:
|
||||
return this._import();
|
||||
return await this._import();
|
||||
case EAction.Materials:
|
||||
return Promise.resolve(this._materials());
|
||||
return await this._materials();
|
||||
case EAction.Voxelise:
|
||||
return Promise.resolve(this._voxelise());
|
||||
return await this._voxelise();
|
||||
case EAction.Assign:
|
||||
return Promise.resolve(this._assign());
|
||||
return await this._assign();
|
||||
case EAction.Export:
|
||||
return Promise.resolve(this._export());
|
||||
return await this._export();
|
||||
}
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
private async _import(): Promise<TWorkerJob> {
|
||||
const uiElements = this._ui.layout.import.components;
|
||||
AppConsole.info('Importing mesh...');
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'Import',
|
||||
params: {
|
||||
file: uiElements.input.getValue(),
|
||||
rotation: uiElements.rotation.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is managed through `AppContext::do`, therefore
|
||||
// this callback is only called if the job is successful.
|
||||
ASSERT(payload.action === 'Import');
|
||||
AppConsole.success('Imported mesh');
|
||||
|
||||
const dimensions = new Vector3(
|
||||
payload.result.dimensions.x,
|
||||
payload.result.dimensions.y,
|
||||
payload.result.dimensions.z,
|
||||
);
|
||||
dimensions.mulScalar(AppConfig.Get.CONSTRAINT_MAXIMUM_HEIGHT / 8.0).floor();
|
||||
this.maxConstraint = dimensions;
|
||||
this._materialManager = new MaterialMapManager(payload.result.materials);
|
||||
|
||||
if (payload.result.triangleCount < AppConfig.Get.RENDER_TRIANGLE_THRESHOLD) {
|
||||
this._workerController.addJob(this._renderMesh());
|
||||
} else {
|
||||
AppConsole.warning(`Will not render mesh as its over ${AppConfig.Get.RENDER_TRIANGLE_THRESHOLD.toLocaleString()} triangles.`);
|
||||
}
|
||||
|
||||
this._updateMaterialsAction();
|
||||
};
|
||||
callback.bind(this);
|
||||
|
||||
return { id: 'Import', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
private _materials(): TWorkerJob {
|
||||
AppConsole.info('Updating materials...');
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'SetMaterials',
|
||||
params: {
|
||||
materials: this._materialManager.materials,
|
||||
},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is managed through `AppContext::do`, therefore
|
||||
// this callback is only called if the job is successful.
|
||||
ASSERT(payload.action === 'SetMaterials');
|
||||
AppConsole.success('Updated materials');
|
||||
|
||||
// The material map shouldn't need updating because the materials
|
||||
// returned from the worker **should** be the same as the materials
|
||||
// sent.
|
||||
{
|
||||
//this._materialMap = payload.result.materials;
|
||||
//this._onMaterialMapChanged();
|
||||
}
|
||||
|
||||
payload.result.materialsChanged.forEach((materialName) => {
|
||||
const material = this._materialManager.materials.get(materialName);
|
||||
ASSERT(material !== undefined);
|
||||
Renderer.Get.recreateMaterialBuffer(materialName, material);
|
||||
Renderer.Get.setModelToUse(MeshType.TriangleMesh);
|
||||
});
|
||||
|
||||
this._ui.enableTo(EAction.Voxelise);
|
||||
};
|
||||
|
||||
return { id: 'Import', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
private _updateMaterialsAction() {
|
||||
this._ui.layoutDull['materials'].components = {};
|
||||
this._ui.layoutDull['materials'].componentOrder = [];
|
||||
|
||||
if (this._materialManager.materials.size == 0) {
|
||||
this._ui.layoutDull['materials'].components[`placeholder_element`] = new PlaceholderComponent('No materials loaded');
|
||||
this._ui.layoutDull['materials'].componentOrder.push(`placeholder_element`);
|
||||
} else {
|
||||
this._materialManager.materials.forEach((material, materialName) => {
|
||||
if (material.type === MaterialType.solid) {
|
||||
this._ui.layoutDull['materials'].components[`mat_${materialName}`] = new SolidMaterialComponent(materialName, material)
|
||||
.setLabel(materialName)
|
||||
.onChangeTypeDelegate(() => {
|
||||
this._materialManager.changeMaterialType(materialName, MaterialType.textured);
|
||||
this._updateMaterialsAction();
|
||||
});
|
||||
} else {
|
||||
this._ui.layoutDull['materials'].components[`mat_${materialName}`] = new TexturedMaterialComponent(materialName, material)
|
||||
.setLabel(materialName)
|
||||
.onChangeTypeDelegate(() => {
|
||||
this._materialManager.changeMaterialType(materialName, MaterialType.solid);
|
||||
this._updateMaterialsAction();
|
||||
})
|
||||
.onChangeTransparencyTypeDelegate((newTransparency) => {
|
||||
this._materialManager.changeTransparencyType(materialName, newTransparency);
|
||||
this._updateMaterialsAction();
|
||||
});
|
||||
}
|
||||
|
||||
this._ui.layoutDull['materials'].componentOrder.push(`mat_${materialName}`);
|
||||
});
|
||||
}
|
||||
|
||||
this._ui.refreshComponents(EAction.Materials);
|
||||
}
|
||||
|
||||
private _renderMesh(): TWorkerJob {
|
||||
AppConsole.info('Rendering mesh...');
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'RenderMesh',
|
||||
params: {},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is not managed through `AppContext::do`, therefore
|
||||
// we need to check the payload is not an error
|
||||
this._ui.enableTo(EAction.Voxelise);
|
||||
|
||||
switch (payload.action) {
|
||||
case 'KnownError':
|
||||
case 'UnknownError': {
|
||||
AppConsole.error(payload.action === 'KnownError' ? payload.error.message : 'Could not render mesh');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ASSERT(payload.action === 'RenderMesh');
|
||||
this._addWorkerMessagesToConsole(payload.messages);
|
||||
AppConsole.success('Rendered mesh');
|
||||
|
||||
Renderer.Get.useMesh(payload.result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { id: 'RenderMesh', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
private _voxelise(): TWorkerJob {
|
||||
AppConsole.info('Loading voxel mesh...');
|
||||
|
||||
const uiElements = this._ui.layout.voxelise.components;
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'Voxelise',
|
||||
params: {
|
||||
constraintAxis: uiElements.constraintAxis.getValue(),
|
||||
voxeliser: uiElements.voxeliser.getValue(),
|
||||
size: uiElements.size.getValue(),
|
||||
useMultisampleColouring: uiElements.multisampleColouring.getValue(),
|
||||
enableAmbientOcclusion: uiElements.ambientOcclusion.getValue(),
|
||||
voxelOverlapRule: uiElements.voxelOverlapRule.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is managed through `AppContext::do`, therefore
|
||||
// this callback is only called if the job is successful.
|
||||
ASSERT(payload.action === 'Voxelise');
|
||||
AppConsole.success('Loaded voxel mesh');
|
||||
|
||||
this._workerController.addJob(this._renderVoxelMesh(true));
|
||||
};
|
||||
|
||||
return { id: 'Voxelise', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
private _renderVoxelMesh(firstChunk: boolean): TWorkerJob {
|
||||
if (firstChunk) {
|
||||
AppConsole.info('Rendering voxel mesh...');
|
||||
}
|
||||
|
||||
const uiElements = this._ui.layout.voxelise.components;
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'RenderNextVoxelMeshChunk',
|
||||
params: {
|
||||
enableAmbientOcclusion: uiElements.ambientOcclusion.getValue(),
|
||||
desiredHeight: uiElements.size.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is not managed through `AppContext::do`, therefore
|
||||
// we need to check the payload is not an error
|
||||
|
||||
switch (payload.action) {
|
||||
case 'KnownError':
|
||||
case 'UnknownError': {
|
||||
AppConsole.error(payload.action === 'KnownError' ? payload.error.message : 'Could not render voxel mesh');
|
||||
this._ui.enableTo(EAction.Assign);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ASSERT(payload.action === 'RenderNextVoxelMeshChunk');
|
||||
this._addWorkerMessagesToConsole(payload.messages);
|
||||
|
||||
Renderer.Get.useVoxelMeshChunk(payload.result);
|
||||
|
||||
if (payload.result.moreVoxelsToBuffer) {
|
||||
this._workerController.addJob(this._renderVoxelMesh(false));
|
||||
} else {
|
||||
AppConsole.success('Rendered voxel mesh');
|
||||
this._ui.enableTo(EAction.Assign);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { id: 'RenderNextVoxelMeshChunk', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
private _assign(): (TWorkerJob | undefined) {
|
||||
const uiElements = this._ui.layout.assign.components;
|
||||
|
||||
if (uiElements.blockPalette.getValue().count() <= 0) {
|
||||
AppConsole.error('No blocks selected');
|
||||
return;
|
||||
}
|
||||
AppConsole.info('Loading block mesh...');
|
||||
|
||||
Renderer.Get.setLightingAvailable(uiElements.calculateLighting.getValue());
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'Assign',
|
||||
params: {
|
||||
textureAtlas: uiElements.textureAtlas.getValue(),
|
||||
blockPalette: uiElements.blockPalette.getValue().getBlocks(),
|
||||
dithering: uiElements.dithering.getValue(),
|
||||
colourSpace: ColourSpace.RGB,
|
||||
fallable: uiElements.fallable.getValue() as FallableBehaviour,
|
||||
resolution: Math.pow(2, uiElements.colourAccuracy.getValue()),
|
||||
calculateLighting: uiElements.calculateLighting.getValue(),
|
||||
lightThreshold: uiElements.lightThreshold.getValue(),
|
||||
contextualAveraging: uiElements.contextualAveraging.getValue(),
|
||||
errorWeight: uiElements.errorWeight.getValue() / 10,
|
||||
},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is managed through `AppContext::do`, therefore
|
||||
// this callback is only called if the job is successful.
|
||||
ASSERT(payload.action === 'Assign');
|
||||
AppConsole.success('Loaded block mesh');
|
||||
|
||||
this._workerController.addJob(this._renderBlockMesh(true));
|
||||
};
|
||||
|
||||
return { id: 'Assign', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
private _renderBlockMesh(firstChunk: boolean): TWorkerJob {
|
||||
if (firstChunk) {
|
||||
AppConsole.info('Rendering block mesh...');
|
||||
}
|
||||
|
||||
const uiElements = this._ui.layout.assign.components;
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'RenderNextBlockMeshChunk',
|
||||
params: {
|
||||
textureAtlas: uiElements.textureAtlas.getValue(),
|
||||
},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is not managed through `AppContext::do`, therefore
|
||||
// we need to check the payload is not an error
|
||||
|
||||
switch (payload.action) {
|
||||
case 'KnownError':
|
||||
case 'UnknownError': {
|
||||
AppConsole.error(payload.action === 'KnownError' ? payload.error.message : 'Could not draw block mesh');
|
||||
this._ui.enableTo(EAction.Export);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ASSERT(payload.action === 'RenderNextBlockMeshChunk');
|
||||
this._addWorkerMessagesToConsole(payload.messages);
|
||||
|
||||
Renderer.Get.useBlockMeshChunk(payload.result);
|
||||
|
||||
if (payload.result.moreBlocksToBuffer) {
|
||||
this._workerController.addJob(this._renderBlockMesh(false));
|
||||
} else {
|
||||
AppConsole.success('Rendered block mesh');
|
||||
this._ui.enableTo(EAction.Export);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { id: 'RenderNextBlockMeshChunk', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
private _export(): (TWorkerJob | undefined) {
|
||||
AppConsole.info('Exporting structure...');
|
||||
|
||||
const exporterID: TExporters = this._ui.layout.export.components.export.getValue();
|
||||
const filepath = '';
|
||||
|
||||
const payload: TToWorkerMessage = {
|
||||
action: 'Export',
|
||||
params: {
|
||||
filepath: filepath,
|
||||
exporter: exporterID,
|
||||
},
|
||||
};
|
||||
|
||||
const callback = (payload: TFromWorkerMessage) => {
|
||||
// This callback is managed through `AppContext::do`, therefore
|
||||
// this callback is only called if the job is successful.
|
||||
ASSERT(payload.action === 'Export');
|
||||
AppConsole.success('Exported structure');
|
||||
|
||||
download(payload.result.buffer, 'result.' + payload.result.extension);
|
||||
|
||||
this._ui.enableTo(EAction.Export);
|
||||
};
|
||||
|
||||
return { id: 'Export', payload: payload, callback: callback };
|
||||
}
|
||||
|
||||
public draw() {
|
||||
Renderer.Get.update();
|
||||
this._ui.tick(this._workerController.isBusy());
|
||||
|
@ -1,123 +1,128 @@
|
||||
import { UIUtil } from '../../util/ui_util';
|
||||
import { BaseComponent } from './base';
|
||||
|
||||
export class ButtonComponent extends BaseComponent<HTMLDivElement> {
|
||||
private _label: string;
|
||||
private _onClick: () => void;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
this._label = 'Unknown';
|
||||
this._onClick = () => { };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delegate that is called when this button is clicked.
|
||||
*/
|
||||
public setOnClick(delegate: () => void) {
|
||||
this._onClick = delegate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label of this button.
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this._label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the current label with a new value.
|
||||
*/
|
||||
public setLabelOverride(label: string) {
|
||||
this._getElement().innerHTML = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the label override and set the label back to its default
|
||||
*/
|
||||
public removeLabelOverride() {
|
||||
this._getElement().innerHTML = this._label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the loading animation
|
||||
*/
|
||||
public startLoading() {
|
||||
this._getElement().classList.add('button-loading');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the progress bar progress.
|
||||
* @param progress A number between 0.0 and 1.0 inclusive.
|
||||
*/
|
||||
public setProgress(progress: number) {
|
||||
const progressBarElement = UIUtil.getElementById(this._getProgressBarId());
|
||||
progressBarElement.style.width = `${progress * 100}%`;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the loading animation
|
||||
*/
|
||||
public stopLoading() {
|
||||
this._getElement().classList.remove('button-loading');
|
||||
return this;
|
||||
}
|
||||
|
||||
public override generateHTML() {
|
||||
return `
|
||||
<div class="container-button">
|
||||
<div class="struct-prop button" id="${this._getId()}">
|
||||
<div class="button-label">${this._label}</div>
|
||||
<div class="button-progress" id="${this._getProgressBarId()}"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public override registerEvents(): void {
|
||||
this._getElement().addEventListener('click', () => {
|
||||
if (this.enabled) {
|
||||
this._onClick?.();
|
||||
}
|
||||
});
|
||||
|
||||
this._getElement().addEventListener('mouseenter', () => {
|
||||
this._setHovered(true);
|
||||
this._updateStyles();
|
||||
});
|
||||
|
||||
this._getElement().addEventListener('mouseleave', () => {
|
||||
this._setHovered(false);
|
||||
this._updateStyles();
|
||||
});
|
||||
}
|
||||
|
||||
protected override _onEnabledChanged() {
|
||||
this._updateStyles();
|
||||
}
|
||||
|
||||
public override finalise(): void {
|
||||
this._updateStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the DOM element for the button's progress bar.
|
||||
*/
|
||||
private _getProgressBarId() {
|
||||
return this._getId() + '-progress';
|
||||
}
|
||||
|
||||
protected _updateStyles(): void {
|
||||
UIUtil.updateStyles(this._getElement(), {
|
||||
isActive: true,
|
||||
isEnabled: this.enabled,
|
||||
isHovered: this.hovered,
|
||||
});
|
||||
}
|
||||
}
|
||||
import { UIUtil } from '../../util/ui_util';
|
||||
import { BaseComponent } from './base';
|
||||
|
||||
export class ButtonComponent extends BaseComponent<HTMLDivElement> {
|
||||
private _label: string;
|
||||
private _onClick: () => void;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
this._label = 'Unknown';
|
||||
this._onClick = () => { };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delegate that is called when this button is clicked.
|
||||
*/
|
||||
public setOnClick(delegate: () => void) {
|
||||
this._onClick = delegate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label of this button.
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this._label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the current label with a new value.
|
||||
*/
|
||||
public setLabelOverride(label: string) {
|
||||
this._getElement().innerHTML = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the label override and set the label back to its default
|
||||
*/
|
||||
public removeLabelOverride() {
|
||||
this._getElement().innerHTML = this._label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the loading animation
|
||||
*/
|
||||
public startLoading() {
|
||||
this._getElement().classList.add('button-loading');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the progress bar progress.
|
||||
* @param progress A number between 0.0 and 1.0 inclusive.
|
||||
*/
|
||||
public setProgress(progress: number) {
|
||||
const progressBarElement = UIUtil.getElementById(this._getProgressBarId());
|
||||
progressBarElement.style.width = `${progress * 100}%`;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the loading animation
|
||||
*/
|
||||
public stopLoading() {
|
||||
this._getElement().classList.remove('button-loading');
|
||||
return this;
|
||||
}
|
||||
|
||||
public resetLoading() {
|
||||
this.stopLoading();
|
||||
this.setProgress(0.0);
|
||||
}
|
||||
|
||||
public override generateHTML() {
|
||||
return `
|
||||
<div class="container-button">
|
||||
<div class="struct-prop button" id="${this._getId()}">
|
||||
<div class="button-label">${this._label}</div>
|
||||
<div class="button-progress" id="${this._getProgressBarId()}"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public override registerEvents(): void {
|
||||
this._getElement().addEventListener('click', () => {
|
||||
if (this.enabled) {
|
||||
this._onClick?.();
|
||||
}
|
||||
});
|
||||
|
||||
this._getElement().addEventListener('mouseenter', () => {
|
||||
this._setHovered(true);
|
||||
this._updateStyles();
|
||||
});
|
||||
|
||||
this._getElement().addEventListener('mouseleave', () => {
|
||||
this._setHovered(false);
|
||||
this._updateStyles();
|
||||
});
|
||||
}
|
||||
|
||||
protected override _onEnabledChanged() {
|
||||
this._updateStyles();
|
||||
}
|
||||
|
||||
public override finalise(): void {
|
||||
this._updateStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the DOM element for the button's progress bar.
|
||||
*/
|
||||
private _getProgressBarId() {
|
||||
return this._getId() + '-progress';
|
||||
}
|
||||
|
||||
protected _updateStyles(): void {
|
||||
UIUtil.updateStyles(this._getElement(), {
|
||||
isActive: true,
|
||||
isEnabled: this.enabled,
|
||||
isHovered: this.hovered,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { BaseComponent } from './base';
|
||||
export abstract class ConfigComponent<T, F> extends BaseComponent<F> {
|
||||
protected _label: string;
|
||||
private _value?: T;
|
||||
private _cachedValue?: T;
|
||||
private _onValueChangedListeners: Array<(newValue: T) => void>;
|
||||
private _onEnabledChangedListeners: Array<(isEnabled: boolean) => void>;
|
||||
|
||||
@ -30,20 +29,6 @@ export abstract class ConfigComponent<T, F> extends BaseComponent<F> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the current value.
|
||||
*/
|
||||
public cacheValue() {
|
||||
this._cachedValue = this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the value stored is different from the cached value.
|
||||
*/
|
||||
public hasChanged() {
|
||||
return this._cachedValue !== this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently set value of this UI element.
|
||||
*/
|
||||
|
@ -4,11 +4,11 @@ import { AppContext } from '../app_context';
|
||||
import { FallableBehaviour } from '../block_mesh';
|
||||
import { ArcballCamera } from '../camera';
|
||||
import { TExporters } from '../exporters/exporters';
|
||||
import { PaletteManager, TPalettes } from '../palette';
|
||||
import { MaterialMapManager } from '../material-map';
|
||||
import { MaterialType } from '../mesh';
|
||||
import { MeshType, Renderer } from '../renderer';
|
||||
import { EAction } from '../util';
|
||||
import { ASSERT } from '../util/error_util';
|
||||
import { LOG } from '../util/log_util';
|
||||
import { TAxis } from '../util/type_util';
|
||||
import { TDithering } from '../util/type_util';
|
||||
import { UIUtil } from '../util/ui_util';
|
||||
@ -16,12 +16,15 @@ import { TVoxelOverlapRule } from '../voxel_mesh';
|
||||
import { TVoxelisers } from '../voxelisers/voxelisers';
|
||||
import { ButtonComponent } from './components/button';
|
||||
import { CheckboxComponent } from './components/checkbox';
|
||||
import { ComboboxComponent, ComboBoxItem } from './components/combobox';
|
||||
import { ComboboxComponent } from './components/combobox';
|
||||
import { ConfigComponent } from './components/config';
|
||||
import { FileComponent } from './components/file_input';
|
||||
import { HeaderComponent } from './components/header';
|
||||
import { PaletteComponent } from './components/palette';
|
||||
import { PlaceholderComponent } from './components/placeholder';
|
||||
import { SliderComponent } from './components/slider';
|
||||
import { SolidMaterialComponent } from './components/solid_material';
|
||||
import { TexturedMaterialComponent } from './components/textured_material';
|
||||
import { ToolbarItemComponent } from './components/toolbar_item';
|
||||
import { VectorSpinboxComponent } from './components/vector_spinbox';
|
||||
import { AppConsole } from './console';
|
||||
@ -512,15 +515,6 @@ export class UI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the current value of each component in an action group.
|
||||
*/
|
||||
public cacheValues(action: EAction) {
|
||||
this._forEachComponent(action, (component) => {
|
||||
component.cacheValue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the HTML for all components in an action group.
|
||||
*/
|
||||
@ -630,11 +624,11 @@ export class UI {
|
||||
*/
|
||||
public disable(action: EAction) {
|
||||
for (let i = action; i < EAction.MAX; ++i) {
|
||||
this._forEachComponent(action, (component) => {
|
||||
this._forEachComponent(i, (component) => {
|
||||
component.setEnabled(false);
|
||||
});
|
||||
|
||||
this._getGroup(action).execButton.setEnabled(false);
|
||||
this._getGroup(i).execButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -652,4 +646,40 @@ export class UI {
|
||||
const key = this.uiOrder[action];
|
||||
return this._uiDull[key];
|
||||
}
|
||||
|
||||
public updateMaterialsAction(materialManager: MaterialMapManager) {
|
||||
this.layout.materials.components = {};
|
||||
this.layout.materials.componentOrder = [];
|
||||
|
||||
if (materialManager.materials.size == 0) {
|
||||
this.layoutDull['materials'].components[`placeholder_element`] = new PlaceholderComponent('No materials loaded');
|
||||
this.layoutDull['materials'].componentOrder.push(`placeholder_element`);
|
||||
} else {
|
||||
materialManager.materials.forEach((material, materialName) => {
|
||||
if (material.type === MaterialType.solid) {
|
||||
this.layoutDull['materials'].components[`mat_${materialName}`] = new SolidMaterialComponent(materialName, material)
|
||||
.setLabel(materialName)
|
||||
.onChangeTypeDelegate(() => {
|
||||
materialManager.changeMaterialType(materialName, MaterialType.textured);
|
||||
this.updateMaterialsAction(materialManager);
|
||||
});
|
||||
} else {
|
||||
this.layoutDull['materials'].components[`mat_${materialName}`] = new TexturedMaterialComponent(materialName, material)
|
||||
.setLabel(materialName)
|
||||
.onChangeTypeDelegate(() => {
|
||||
materialManager.changeMaterialType(materialName, MaterialType.solid);
|
||||
this.updateMaterialsAction(materialManager);
|
||||
})
|
||||
.onChangeTransparencyTypeDelegate((newTransparency) => {
|
||||
materialManager.changeTransparencyType(materialName, newTransparency);
|
||||
this.updateMaterialsAction(materialManager);
|
||||
});
|
||||
}
|
||||
|
||||
this.layoutDull['materials'].componentOrder.push(`mat_${materialName}`);
|
||||
});
|
||||
}
|
||||
|
||||
this.refreshComponents(EAction.Materials);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { AppConfig } from './config';
|
||||
import { EAppEvent, EventManager } from './event';
|
||||
import { ASSERT } from './util/error_util';
|
||||
import { AppError, ASSERT } from './util/error_util';
|
||||
import { LOG } from './util/log_util';
|
||||
import { doWork } from './worker';
|
||||
// @ts-ignore
|
||||
@ -33,6 +33,19 @@ export class WorkerController {
|
||||
this._timerOn = false;
|
||||
}
|
||||
|
||||
public async execute(payload: TToWorkerMessage): Promise<TFromWorkerMessage> {
|
||||
return new Promise((res, rej) => {
|
||||
const success = this.addJob({
|
||||
id: 'ExecuteJob',
|
||||
payload: payload,
|
||||
callback: res,
|
||||
});
|
||||
if (!success) {
|
||||
rej(new AppError('Already performing a job'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public addJob(newJob: TWorkerJob): boolean {
|
||||
const isJobAlreadyQueued = this._jobQueue.some((queuedJob) => { return queuedJob.id === newJob.id; });
|
||||
if (isJobAlreadyQueued) {
|
||||
|
Loading…
Reference in New Issue
Block a user