Fixed headless, improved usability and logging

This commit is contained in:
Lucas Dower 2022-09-11 17:38:22 +01:00
parent 88e754325d
commit 80a8454fae
23 changed files with 325 additions and 366 deletions

View File

@ -31,6 +31,8 @@
"no-unused-vars": "warn",
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"block-spacing": [2, "always"],
"semi": "error"
"semi": "error",
"spaced-comment": "off",
"keyword-spacing": "off"
}
}

View File

@ -7,15 +7,13 @@
"node": ">=14.0.0"
},
"scripts": {
"lint": "eslint --fix ./src/**/*.ts && eslint --fix ./tools/**/*.ts",
"debug": "tsc && electron ./dist/src/main.js --enable-logging",
"build": "npm run lint && tsc",
"fast-build": "tsc",
"lint": "eslint --fix ./**/*.ts",
"build": "tsc",
"test": "jest --config jestconfig.json",
"start": "npm run build && electron ./dist/src/main.js --enable-logging",
"atlas": "node ./dist/tools/build-atlas.js",
"palette": "node ./dist/tools/build-palette.js",
"headless": "node ./dist/tools/headless.js",
"headless": "tsc && node ./dist/tools/headless.js",
"package:win": "electron-packager . ObjToSchematic --overwrite --platform=win32 --arch=x64 --app-version=0.5.1 --prune=true",
"package:linux": "electron-packager . ObjToSchematic --overwrite --platform=linux --arch=x64 --icon=res/static/icon.png --app-version=0.5.1 --prune=true",
"package:macos": "electron-packager . ObjToSchematic --overwrite --platform=darwin --arch=x64 --icon=res/static/icon.icns --app-version=0.5.1 --prune=true"

View File

@ -7,7 +7,7 @@ import { ArcballCamera } from './camera';
import path from 'path';
import { TWorkerJob, WorkerController } from './worker_controller';
import { TFromWorkerMessage, TToWorkerMessage } from './worker_types';
import { LOG } from './util/log_util';
import { Logger } from './util/log_util';
import { ASSERT } from './util/error_util';
import { ColourSpace, EAction } from './util';
import { AppConfig } from './config';
@ -22,6 +22,9 @@ export class AppContext {
private _ui: UI;
private _workerController: WorkerController;
public constructor() {
Logger.Get.enableLOG();
Logger.Get.enableLOGMAJOR();
const gl = (<HTMLCanvasElement>document.getElementById('canvas')).getContext('webgl');
if (!gl) {
throw Error('Could not load WebGL context');
@ -48,7 +51,7 @@ export class AppContext {
this._ui.enable(action);
return;
}
const uiOutput = this._ui.getActionOutput(action);
const jobCallback = (payload: TFromWorkerMessage) => {
@ -59,14 +62,14 @@ export class AppContext {
uiOutput.setTaskComplete(
'action',
StatusHandler.Get.getDefaultFailureMessage(action),
[ payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong' ],
'error'
[payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong'],
'error',
);
break;
}
default: {
this._ui.enable(action + 1);
const { builder, style } = this._getActionMessageBuilder(action, payload.statusMessages);
uiOutput.setMessage(builder, style as OutputStyle);
@ -75,7 +78,7 @@ export class AppContext {
}
}
}
}
};
this._workerController.addJob({
id: workerJob.id,
@ -86,13 +89,13 @@ export class AppContext {
private _getActionMessageBuilder(action: EAction, statusMessages: StatusMessage[]) {
const infoStatuses = statusMessages
.filter(x => x.status === 'info')
.map(x => x.message);
.filter((x) => x.status === 'info')
.map((x) => x.message);
const hasInfos = infoStatuses.length > 0;
const warningStatuses = statusMessages
.filter(x => x.status === 'warning')
.map(x => x.message);
.filter((x) => x.status === 'warning')
.map((x) => x.message);
const hasWarnings = warningStatuses.length > 0;
const builder = new UIMessageBuilder();
@ -123,15 +126,15 @@ export class AppContext {
private _import(): TWorkerJob {
const uiElements = this._ui.layout.import.elements;
this._ui.getActionOutput(EAction.Import)
.setTaskInProgress('action', '[Importer]: Loading...');
const payload: TToWorkerMessage = {
action: 'Import',
params: {
filepath: uiElements.input.getCachedValue()
}
filepath: uiElements.input.getCachedValue(),
},
};
const callback = (payload: TFromWorkerMessage) => {
@ -142,10 +145,10 @@ export class AppContext {
if (payload.result.triangleCount < AppConfig.RENDER_TRIANGLE_THRESHOLD) {
this._workerController.addJob(this._renderMesh());
outputElement.setTaskInProgress('render', '[Renderer]: Processing...')
outputElement.setTaskInProgress('render', '[Renderer]: Processing...');
} else {
const message = `Will not render mesh as its over ${AppConfig.RENDER_TRIANGLE_THRESHOLD} triangles.`;
outputElement.setTaskComplete('render', '[Renderer]: Stopped.', [ message ], 'warning')
outputElement.setTaskComplete('render', '[Renderer]: Stopped.', [message], 'warning');
}
};
@ -155,7 +158,7 @@ export class AppContext {
private _renderMesh(): TWorkerJob {
const payload: TToWorkerMessage = {
action: 'RenderMesh',
params: {}
params: {},
};
const callback = (payload: TFromWorkerMessage) => {
@ -167,8 +170,8 @@ export class AppContext {
this._ui.getActionOutput(EAction.Import).setTaskComplete(
'render',
'[Renderer]: Failed',
[ payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong' ],
'error'
[payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong'],
'error',
);
break;
}
@ -180,8 +183,8 @@ export class AppContext {
'render',
'[Renderer]: Succeeded',
[],
'success'
)
'success',
);
}
}
};
@ -215,7 +218,7 @@ export class AppContext {
const outputElement = this._ui.getActionOutput(EAction.Voxelise);
this._workerController.addJob(this._renderVoxelMesh());
outputElement.setTaskInProgress('render', '[Renderer]: Processing...')
outputElement.setTaskInProgress('render', '[Renderer]: Processing...');
};
return { id: 'Voxelise', payload: payload, callback: callback };
@ -241,21 +244,21 @@ export class AppContext {
this._ui.getActionOutput(EAction.Voxelise).setTaskComplete(
'render',
'[Renderer]: Failed',
[ payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong' ],
'error'
[payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong'],
'error',
);
break;
}
default: {
ASSERT(payload.action === 'RenderVoxelMesh');
Renderer.Get.useVoxelMesh(payload.result)
Renderer.Get.useVoxelMesh(payload.result);
this._ui.getActionOutput(EAction.Voxelise).setTaskComplete(
'render',
'[Renderer]: Succeeded',
[],
'success'
)
'success',
);
}
}
};
@ -287,7 +290,7 @@ export class AppContext {
const outputElement = this._ui.getActionOutput(EAction.Assign);
this._workerController.addJob(this._renderBlockMesh());
outputElement.setTaskInProgress('render', '[Renderer]: Processing...')
outputElement.setTaskInProgress('render', '[Renderer]: Processing...');
};
return { id: 'Assign', payload: payload, callback: callback };
@ -312,21 +315,21 @@ export class AppContext {
this._ui.getActionOutput(EAction.Assign).setTaskComplete(
'render',
'[Renderer]: Failed',
[ payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong' ],
'error'
[payload.action === 'KnownError' ? payload.error.message : 'Something unexpectedly went wrong'],
'error',
);
break;
}
default: {
ASSERT(payload.action === 'RenderBlockMesh');
Renderer.Get.useBlockMesh(payload.result)
Renderer.Get.useBlockMesh(payload.result);
this._ui.getActionOutput(EAction.Assign).setTaskComplete(
'render',
'[Renderer]: Succeeded',
[],
'success'
)
'success',
);
}
}
};
@ -338,7 +341,7 @@ export class AppContext {
const exporterID: TExporters = this._ui.layout.export.elements.export.getCachedValue();
const exporter: IExporter = ExporterFactory.GetExporter(exporterID);
let filepath = remote.dialog.showSaveDialogSync({
const filepath = remote.dialog.showSaveDialogSync({
title: 'Save structure',
buttonLabel: 'Save',
filters: [exporter.getFormatFilter()],
@ -356,7 +359,7 @@ export class AppContext {
params: {
filepath: filepath,
exporter: exporterID,
}
},
};
const callback = (payload: TFromWorkerMessage) => {

View File

@ -1,8 +1,6 @@
import { Voxel, VoxelMesh } from './voxel_mesh';
import { BlockInfo } from './block_atlas';
import { ColourSpace, RESOURCES_DIR } from './util';
import { Renderer } from './renderer';
import { AppConstants } from './constants';
import fs from 'fs';
import path from 'path';
@ -14,6 +12,7 @@ import { BlockAssignerFactory, TBlockAssigners } from './assigners/assigners';
import { AtlasPalette } from './block_assigner';
import { AppError, ASSERT } from './util/error_util';
import { AssignParams } from './worker_types';
import { BufferGenerator, TBlockMeshBufferDescription } from './buffer';
interface Block {
voxel: Voxel;
@ -48,6 +47,7 @@ export class BlockMesh {
this._blocks = [];
this._voxelMesh = voxelMesh;
this._atlas = Atlas.getVanillaAtlas()!;
//this._recreateBuffer = true;
const fallableBlocksString = fs.readFileSync(path.join(RESOURCES_DIR, 'fallable_blocks.json'), 'utf-8');
this._fallableBlocks = JSON.parse(fallableBlocksString).fallable_blocks;
@ -64,7 +64,7 @@ export class BlockMesh {
const atlasPalette = new AtlasPalette(atlas, palette);
const blockAssigner = BlockAssignerFactory.GetAssigner(blockMeshParams.blockAssigner);
let countFalling = 0;
const voxels = this._voxelMesh.getVoxels();
for (let voxelIndex = 0; voxelIndex < voxels.length; ++voxelIndex) {
@ -73,7 +73,7 @@ export class BlockMesh {
const isFallable = this._fallableBlocks.includes(block.name);
const isSupported = this._voxelMesh.isVoxelAt(Vector3.add(voxel.position, new Vector3(0, -1, 0)));
if (isFallable && !isSupported) {
++countFalling;
}
@ -116,63 +116,26 @@ export class BlockMesh {
return this._voxelMesh;
}
/*
public createBuffer() {
ASSERT(this._blocks.length === this._voxelMesh.getVoxelCount());
// FIXME: Too hacky
const voxelBufferRaw = (typeof window === 'undefined') ? this._voxelMesh.createBuffer(false) : Renderer.Get._voxelBufferRaw!;
const numBlocks = this._blocks.length;
const newBuffer = {
position: {
numComponents: AppConstants.ComponentSize.POSITION,
data: voxelBufferRaw.position.data,
},
colour: {
numComponents: AppConstants.ComponentSize.COLOUR,
data: voxelBufferRaw.colour.data,
},
occlusion: {
numComponents: AppConstants.ComponentSize.OCCLUSION,
data: voxelBufferRaw.occlusion.data,
},
texcoord: {
numComponents: AppConstants.ComponentSize.TEXCOORD,
data: voxelBufferRaw.texcoord.data,
},
normal: {
numComponents: AppConstants.ComponentSize.NORMAL,
data: voxelBufferRaw.normal.data,
},
indices: {
numComponents: AppConstants.ComponentSize.INDICES,
data: voxelBufferRaw.indices.data,
},
blockTexcoord: {
numComponents: AppConstants.ComponentSize.TEXCOORD,
data: new Float32Array(numBlocks * AppConstants.VoxelMeshBufferComponentOffsets.TEXCOORD),
},
};
const faceOrder = ['north', 'south', 'up', 'down', 'east', 'west'];
let insertIndex = 0;
for (let i = 0; i < numBlocks; ++i) {
for (let f = 0; f < AppConstants.FACES_PER_VOXEL; ++f) {
const faceName = faceOrder[f];
const texcoord = this._blocks[i].blockInfo.faces[faceName].texcoord;
for (let v = 0; v < AppConstants.VERTICES_PER_FACE; ++v) {
newBuffer.blockTexcoord.data[insertIndex++] = texcoord.u;
newBuffer.blockTexcoord.data[insertIndex++] = texcoord.v;
}
}
}
return newBuffer;
}
*/
public getAtlas() {
return this._atlas;
}
/*
private _renderParams?: RenderBlockMeshParams.Input;
private _recreateBuffer: boolean;
public setRenderParams(params: RenderBlockMeshParams.Input) {
this._renderParams = params;
this._recreateBuffer = true;
}
*/
private _buffer?: TBlockMeshBufferDescription;
public getBuffer(): TBlockMeshBufferDescription {
//ASSERT(this._renderParams, 'Called BlockMesh.getBuffer() without setting render params');
if (this._buffer === undefined) {
this._buffer = BufferGenerator.fromBlockMesh(this);
//this._recreateBuffer = false;
}
return this._buffer;
}
}

View File

@ -1,9 +1,9 @@
import { Vector3 } from "./vector";
import { Vector3 } from './vector';
/**
* A 3D cuboid volume defined by two opposing corners
*/
export class Bounds {
export class Bounds {
private _min: Vector3;
private _max: Vector3;
@ -45,4 +45,4 @@ import { Vector3 } from "./vector";
public getDimensions() {
return Vector3.sub(this._max, this._min);
}
}
}

View File

@ -1,12 +1,12 @@
import { GeometryTemplates } from "./geometry";
import { Mesh, SolidMaterial, TexturedMaterial } from "./mesh";
import { AttributeData } from "./render_buffer";
import { Vector3 } from "./vector";
import { VoxelMesh } from "./voxel_mesh";
import { AppConstants } from "./constants";
import { RenderVoxelMeshParams } from "./worker_types";
import { OcclusionManager } from "./occlusion";
import { BlockMesh } from "./block_mesh";
import { GeometryTemplates } from './geometry';
import { Mesh, SolidMaterial, TexturedMaterial } from './mesh';
import { AttributeData } from './render_buffer';
import { Vector3 } from './vector';
import { VoxelMesh } from './voxel_mesh';
import { AppConstants } from './constants';
import { RenderVoxelMeshParams } from './worker_types';
import { OcclusionManager } from './occlusion';
import { BlockMesh } from './block_mesh';
export type TMeshBuffer = {
position: { numComponents: 3, data: Float32Array },
@ -53,7 +53,6 @@ export type TBlockMeshBufferDescription = {
type TMaterialID = string;
export class BufferGenerator {
public static fromMesh(mesh: Mesh): TMeshBufferDescription[] {
// Count the number of triangles that use each material
const materialTriangleCount = new Map<TMaterialID, number>();
@ -168,15 +167,11 @@ export class BufferGenerator {
};
}
public static fromBlockMesh(blockMesh: BlockMesh, voxelMeshBuffer: TVoxelMeshBuffer): TBlockMeshBufferDescription {
//ASSERT(this._blocks.length === this._voxelMesh.getVoxelCount());
//const voxelBufferRaw = (typeof window === 'undefined') ? this._voxelMesh.createBuffer(false) : Renderer.Get._voxelBufferRaw!;
public static fromBlockMesh(blockMesh: BlockMesh): TBlockMeshBufferDescription {
const blocks = blockMesh.getBlocks();
const numBlocks = blocks.length;
const newBuffer = this.createBlockMeshBuffer(numBlocks, voxelMeshBuffer);
const newBuffer = this.createBlockMeshBuffer(numBlocks, blockMesh.getVoxelMesh().getBuffer().buffer);
const faceOrder = ['north', 'south', 'up', 'down', 'east', 'west'];
let insertIndex = 0;
@ -279,5 +274,4 @@ export class BufferGenerator {
},
};
}
}
}

View File

@ -1,5 +1,5 @@
import { ASSERT } from "./util/error_util";
import { LOG } from "./util/log_util";
import { ASSERT } from './util/error_util';
import { LOG } from './util/log_util';
/* eslint-disable */
export enum EAppEvent {

View File

@ -1,7 +1,6 @@
import { Vector3 } from '../vector';
import { BlockMesh } from '../block_mesh';
import { TOptional } from '../util';
import { TBlockMeshBuffer } from '../buffer';
export abstract class IExporter {
protected _sizeVector!: Vector3;
@ -19,7 +18,7 @@ export abstract class IExporter {
return;
}
public abstract export(blockMesh: BlockMesh, filePath: string, blockMeshBuffer: TBlockMeshBuffer): boolean;
public abstract export(blockMesh: BlockMesh, filePath: string): boolean;
public getFormatFilter() {
return {

View File

@ -4,7 +4,6 @@ import { ASSERT } from '../util/error_util';
import fs from 'fs';
import path from 'path';
import { TBlockMeshBuffer } from '../buffer';
export class ObjExporter extends IExporter {
public override getFormatFilter(): Electron.FileFilter {
@ -22,32 +21,34 @@ export class ObjExporter extends IExporter {
return 'Wavefront OBJ';
}
public override export(blockMesh: BlockMesh, filepath: string, blockMeshBuffer: TBlockMeshBuffer) {
public override export(blockMesh: BlockMesh, filepath: string) {
ASSERT(path.isAbsolute(filepath));
const parsedPath = path.parse(filepath);
const filepathOBJ = filepath;
const filepathMTL = path.join(parsedPath.dir, parsedPath.name + '.mtl');
const filepathTexture = path.join(parsedPath.dir, parsedPath.name + '.png');
this._exportOBJ(filepathOBJ, blockMesh, blockMeshBuffer, parsedPath.name + '.mtl');
this._exportOBJ(filepathOBJ, blockMesh, parsedPath.name + '.mtl');
this._exportMTL(filepathMTL, filepathTexture, blockMesh);
return true;
}
private _exportOBJ(filepath: string, blockMesh: BlockMesh, buffer: TBlockMeshBuffer, mtlRelativePath: string) {
private _exportOBJ(filepath: string, blockMesh: BlockMesh, mtlRelativePath: string) {
const buffer = blockMesh.getBuffer().buffer;
const positionData = buffer.position.data as Float32Array;
const normalData = buffer.normal.data as Float32Array;
const texcoordData = buffer.texcoord.data as Float32Array;
const blockTexcoordData = buffer.blockTexcoord.data as Float32Array;
const indexData = buffer.indices.data as Uint32Array;
const writeStream = fs.createWriteStream(filepath);
writeStream.write('# Created with ObjToSchematic\n');
writeStream.write('# https://github.com/LucasDower/ObjToSchematic/\n\n');
if (positionData && normalData && texcoordData && indexData && blockTexcoordData) {
const numTris = indexData.length / 3;
// Add vertex data
@ -90,7 +91,7 @@ export class ObjExporter extends IExporter {
const mtlData: string[] = [];
mtlData.push('# Created with ObjToSchematic');
mtlData.push('# https://github.com/LucasDower/ObjToSchematic/');
mtlData.push('newmtl Default');
mtlData.push('Kd 1.000000 1.000000 1.000000');
mtlData.push(`map_Kd ${filepathTexture}`);

View File

@ -34,7 +34,7 @@ export interface TexturedMaterial {
alphaPath?: string;
alphaFactor: number;
}
export type MaterialMap = {[key: string]: (SolidMaterial | TexturedMaterial)};
export type MaterialMap = { [key: string]: (SolidMaterial | TexturedMaterial) };
export class Mesh {
public readonly id: string;
@ -43,7 +43,7 @@ export class Mesh {
public _normals!: Vector3[];
public _uvs!: UV[];
public _tris!: Tri[];
private _materials!: MaterialMap;
private _loadedTextures: { [materialName: string]: Texture };
public static desiredHeight = 8.0;
@ -170,7 +170,7 @@ export class Mesh {
colour: RGBAColours.WHITE,
};
}
// Check texture paths are absolute and exist
for (const materialName in this._materials) {
const material = this._materials[materialName];
@ -208,7 +208,7 @@ export class Mesh {
centre.divScalar(totalWeight);
*/
const centre = this.getBounds().getCentre();
if (!centre.isNumber()) {
throw new AppError('Could not find centre of mesh');
}

View File

@ -3,15 +3,11 @@ import { ArcballCamera } from './camera';
import { ShaderManager } from './shaders';
import { RenderBuffer } from './render_buffer';
import { DebugGeometryTemplates } from './geometry';
import { Mesh, SolidMaterial, TexturedMaterial, MaterialType } from './mesh';
import { VoxelMesh } from './voxel_mesh';
import { BlockMesh } from './block_mesh';
import { SolidMaterial, TexturedMaterial, MaterialType } from './mesh';
import * as twgl from 'twgl.js';
import { RGBA, RGBAUtil } from './colour';
import { Texture } from './texture';
import { LOG } from './util/log_util';
import { TMeshBufferDescription } from './buffer';
import { RenderBlockMeshParams, RenderMeshParams, RenderVoxelMeshParams } from './worker_types';
/* eslint-disable */
@ -55,7 +51,7 @@ export class Renderer {
numElements: number,
}>;
public _voxelBuffer?: twgl.BufferInfo;
public _voxelBufferRaw?: {[attribute: string]: { numComponents: number, data: Float32Array | Uint32Array }};
public _voxelBufferRaw?: { [attribute: string]: { numComponents: number, data: Float32Array | Uint32Array } };
private _blockBuffer?: twgl.BufferInfo;
private _debugBuffers: { [meshType: string]: { [bufferComponent: string]: RenderBuffer } };
private _axisBuffer: RenderBuffer;
@ -64,9 +60,9 @@ export class Renderer {
private _axesEnabled: boolean;
private _gridBuffers: {
x: { [meshType: string]: RenderBuffer};
y: { [meshType: string]: RenderBuffer};
z: { [meshType: string]: RenderBuffer};
x: { [meshType: string]: RenderBuffer };
y: { [meshType: string]: RenderBuffer };
z: { [meshType: string]: RenderBuffer };
};
private _gridEnabled: boolean;
@ -167,7 +163,7 @@ export class Renderer {
this.setModelToUse(MeshType.None);
}
public useMesh(params: RenderMeshParams.Output) {
public useMesh(params: RenderMeshParams.Output) {
this._materialBuffers = [];
for (const { material, buffer, numElements } of params.buffers) {
@ -226,23 +222,23 @@ export class Renderer {
this._gridBuffers.x[MeshType.VoxelMesh] = DebugGeometryTemplates.gridX(Vector3.mulScalar(dimensions, voxelSize), voxelSize);
this._gridBuffers.y[MeshType.VoxelMesh] = DebugGeometryTemplates.gridY(Vector3.mulScalar(dimensions, voxelSize), voxelSize);
this._gridBuffers.z[MeshType.VoxelMesh] = DebugGeometryTemplates.gridZ(Vector3.mulScalar(dimensions, voxelSize), voxelSize);
this._modelsAvailable = 2;
this.setModelToUse(MeshType.VoxelMesh);
}
public useBlockMesh(params: RenderBlockMeshParams.Output) {
this._blockBuffer = twgl.createBufferInfoFromArrays(this._gl, params.buffer.buffer);
this._atlasTexture = twgl.createTexture(this._gl, {
src: params.atlasTexturePath,
mag: this._gl.NEAREST,
});
this._atlasSize = params.atlasSize,
this._atlasSize = params.atlasSize;
this._gridBuffers.y[MeshType.BlockMesh] = this._gridBuffers.y[MeshType.VoxelMesh];
this._modelsAvailable = 3;
this.setModelToUse(MeshType.BlockMesh);
}
@ -382,7 +378,7 @@ export class Renderer {
}
private _setupScene() {
twgl.resizeCanvasToDisplaySize(<HTMLCanvasElement> this._gl.canvas);
twgl.resizeCanvasToDisplaySize(<HTMLCanvasElement>this._gl.canvas);
this._gl.viewport(0, 0, this._gl.canvas.width, this._gl.canvas.height);
ArcballCamera.Get.aspect = this._gl.canvas.width / this._gl.canvas.height;
this._gl.blendFuncSeparate(this._gl.SRC_ALPHA, this._gl.ONE_MINUS_SRC_ALPHA, this._gl.ONE, this._gl.ONE_MINUS_SRC_ALPHA);
@ -403,7 +399,7 @@ export class Renderer {
public getModelsAvailable() {
return this._modelsAvailable;
}
public getActiveMeshType() {
return this._meshToUse;
}

View File

@ -1,6 +1,6 @@
import { EAction } from './util';
import { ASSERT } from './util/error_util';
import { LOG, LOG_WARN } from './util/log_util';
import { LOG, LOG_MAJOR, LOG_WARN } from './util/log_util';
export type StatusType = 'warning' | 'info';
@ -15,7 +15,7 @@ export class StatusHandler {
public static get Get() {
return this._instance || (this._instance = new this());
}
private _statusMessages: StatusMessage[];
private constructor() {
@ -36,7 +36,7 @@ export class StatusHandler {
}
public getStatusMessages(statusType: StatusType): string[] {
const messagesToReturn = (statusType !== undefined) ? this._statusMessages.filter((m) => m.status === statusType ): this._statusMessages;
const messagesToReturn = (statusType !== undefined) ? this._statusMessages.filter((m) => m.status === statusType) : this._statusMessages;
return messagesToReturn.map((m) => m.message);
}
@ -55,7 +55,7 @@ export class StatusHandler {
case EAction.Export:
return '[Exporter]: Saved';
default:
ASSERT(false)
ASSERT(false);
}
}
@ -73,4 +73,15 @@ export class StatusHandler {
ASSERT(false);
}
}
public dump() {
for (const { message, status } of this._statusMessages) {
if (status === 'warning') {
LOG_WARN(message);
} else {
LOG_MAJOR(' - ' + message);
}
}
return this;
}
}

View File

@ -66,4 +66,4 @@ export const TESTS_DATA_DIR = PathUtil.join(BASE_DIR, './tests/data/');
export function getRandomID(): string {
return (Math.random() + 1).toString(36).substring(7);
}
}

View File

@ -1,7 +1,63 @@
/* eslint-disable */
export const LOG = console.log;
/**
* Performs console.log if logging LOG is enabled
*/
export const LOG = (...data: any[]) => {
if (Logger.Get.isLOGEnabled()) {
console.log(...data);
}
}
/**
* Performs console.log if logging LOG_MAJOR is enabled
*/
export const LOG_MAJOR = (...data: any[]) => {
if (Logger.Get.isLOGMAJOREnabled()) {
console.log(...data);
}
}
export const LOG_WARN = console.warn;
export const LOG_ERROR = console.error;
export const TIME_START = console.time;
export const TIME_END = console.timeEnd;
/* eslint-enable */
export class Logger {
/* Singleton */
private static _instance: Logger;
public static get Get() {
return this._instance || (this._instance = new this());
}
private _enabledLOG = false;
private _enabledLOGMAJOR = false;
private constructor() {
}
public enableLOG() {
this._enabledLOG = true;
}
public disableLOG() {
this._enabledLOG = false;
}
public enableLOGMAJOR() {
this._enabledLOGMAJOR = true;
}
public disableLOGMAJOR() {
this._enabledLOGMAJOR = false;
}
public isLOGEnabled() {
return this._enabledLOG;
}
public isLOGMAJOREnabled() {
return this._enabledLOGMAJOR;
}
}

View File

@ -263,4 +263,4 @@ export const fastCrossYAxis = (vec: Vector3) => {
export const fastCrossZAxis = (vec: Vector3) => {
return new Vector3(-vec.y, vec.x, 0.0);
};
};

View File

@ -1,14 +1,12 @@
import { Bounds } from './bounds';
import { AttributeData } from './render_buffer';
import { RGBA } from './colour';
import { AppConstants } from './constants';
import { GeometryTemplates } from './geometry';
import { HashMap } from './hash_map';
import { OcclusionManager } from './occlusion';
import { TOptional } from './util';
import { ASSERT } from './util/error_util';
import { Vector3 } from './vector';
import { VoxeliseParams } from './worker_types';
import { RenderVoxelMeshParams, VoxeliseParams } from './worker_types';
import { BufferGenerator, TVoxelMeshBufferDescription } from './buffer';
export interface Voxel {
position: Vector3;
@ -18,7 +16,7 @@ export interface Voxel {
export type TVoxelOverlapRule = 'first' | 'average';
export type TVoxelMeshParams = Pick<VoxeliseParams.Input, "voxelOverlapRule" | "calculateNeighbours">;
export type TVoxelMeshParams = Pick<VoxeliseParams.Input, 'voxelOverlapRule' | 'calculateNeighbours'>;
export class VoxelMesh {
private _voxels: (Voxel & { collisions: number })[];
@ -33,6 +31,7 @@ export class VoxelMesh {
this._neighbourMap = new Map();
this._bounds = Bounds.getInfiniteBounds();
this._voxelMeshParams = voxelMeshParams;
this._recreateBuffer = true;
}
public getVoxels() {
@ -149,4 +148,21 @@ export class VoxelMesh {
public hasNeighbour(pos: Vector3, offset: Vector3): boolean {
return (this.getNeighbours(pos).value & (1 << OcclusionManager.getNeighbourIndex(offset))) > 0;
}
private _renderParams?: RenderVoxelMeshParams.Input;
private _recreateBuffer: boolean;
public setRenderParams(params: RenderVoxelMeshParams.Input) {
this._renderParams = params;
this._recreateBuffer = true;
}
private _buffer?: TVoxelMeshBufferDescription;
public getBuffer(): TVoxelMeshBufferDescription {
ASSERT(this._renderParams, 'Called VoxelMesh.getBuffer() without setting render params');
if (this._buffer === undefined || this._recreateBuffer) {
this._buffer = BufferGenerator.fromVoxelMesh(this, this._renderParams);
this._recreateBuffer = false;
}
return this._buffer;
}
}

View File

@ -1,4 +1,3 @@
import { clamp } from './math';
import { WorkerClient } from './worker_client';
import { TToWorkerMessage, TFromWorkerMessage } from './worker_types';
import { StatusHandler } from './status';
@ -20,7 +19,7 @@ export function doWork(message: TToWorkerMessage): TFromWorkerMessage {
result: WorkerClient.Get.renderMesh(message.params),
statusMessages: StatusHandler.Get.getAllStatusMessages(),
};
case 'Voxelise':
case 'Voxelise':
return {
action: 'Voxelise',
result: WorkerClient.Get.voxelise(message.params),
@ -32,7 +31,7 @@ export function doWork(message: TToWorkerMessage): TFromWorkerMessage {
result: WorkerClient.Get.renderVoxelMesh(message.params),
statusMessages: StatusHandler.Get.getAllStatusMessages(),
};
case 'Assign':
case 'Assign':
return {
action: 'Assign',
result: WorkerClient.Get.assign(message.params),
@ -44,7 +43,7 @@ export function doWork(message: TToWorkerMessage): TFromWorkerMessage {
result: WorkerClient.Get.renderBlockMesh(message.params),
statusMessages: StatusHandler.Get.getAllStatusMessages(),
};
case 'Export':
case 'Export':
return {
action: 'Export',
result: WorkerClient.Get.export(message.params),
@ -55,7 +54,6 @@ export function doWork(message: TToWorkerMessage): TFromWorkerMessage {
return { action: e instanceof AppError ? 'KnownError' : 'UnknownError', error: e as Error };
}
return { action: 'KnownError', error: new AppError('Worker could not handle message') };
return { action: 'KnownError', error: new AppError('Worker could not handle message') };
}

View File

@ -1,19 +1,15 @@
import { RenderBuffer } from "./render_buffer";
import { GeometryTemplates } from "./geometry";
import { ObjImporter } from "./importers/obj_importer";
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial } from "./mesh";
import { ASSERT } from "./util/error_util";
import { AssignParams, ExportParams, ImportParams, RenderBlockMeshParams, RenderMeshParams, RenderVoxelMeshParams, VoxeliseParams } from "./worker_types";
import { BufferGenerator, TBlockMeshBuffer, TVoxelMeshBuffer } from "./buffer";
import { TVoxelisers, VoxeliserFactory } from "./voxelisers/voxelisers";
import { param } from "jquery";
import { IVoxeliser } from "./voxelisers/base-voxeliser";
import { TIME_END, TIME_START } from "./util/log_util";
import { VoxelMesh } from "./voxel_mesh";
import { BlockMesh } from "./block_mesh";
import { Atlas } from "./atlas";
import { ExporterFactory } from "./exporters/exporters";
import { IExporter } from "./exporters/base_exporter";
import { ObjImporter } from './importers/obj_importer';
import { Mesh } from './mesh';
import { ASSERT } from './util/error_util';
import { AssignParams, ExportParams, ImportParams, RenderBlockMeshParams, RenderMeshParams, RenderVoxelMeshParams, VoxeliseParams } from './worker_types';
import { BufferGenerator } from './buffer';
import { VoxeliserFactory } from './voxelisers/voxelisers';
import { IVoxeliser } from './voxelisers/base-voxeliser';
import { VoxelMesh } from './voxel_mesh';
import { BlockMesh } from './block_mesh';
import { Atlas } from './atlas';
import { ExporterFactory } from './exporters/exporters';
import { IExporter } from './exporters/base_exporter';
export class WorkerClient {
private static _instance: WorkerClient;
@ -25,9 +21,6 @@ export class WorkerClient {
private _loadedVoxelMesh?: VoxelMesh;
private _loadedBlockMesh?: BlockMesh;
private _voxelMeshBuffer?: TVoxelMeshBuffer;
private _blockMeshBuffer?: TBlockMeshBuffer;
public import(params: ImportParams.Input): ImportParams.Output {
const importer = new ObjImporter();
importer.parseFile(params.filepath);
@ -50,22 +43,21 @@ export class WorkerClient {
public voxelise(params: VoxeliseParams.Input): VoxeliseParams.Output {
ASSERT(this._loadedMesh !== undefined);
const voxeliser: IVoxeliser = VoxeliserFactory.GetVoxeliser(params.voxeliser);
this._loadedVoxelMesh = voxeliser.voxelise(this._loadedMesh, params);
return {
}
};
}
public renderVoxelMesh(params: RenderVoxelMeshParams.Input): RenderVoxelMeshParams.Output {
ASSERT(this._loadedVoxelMesh !== undefined);
const buffer = BufferGenerator.fromVoxelMesh(this._loadedVoxelMesh, params);
this._voxelMeshBuffer = buffer.buffer;
this._loadedVoxelMesh.setRenderParams(params);
return {
buffer: buffer,
buffer: this._loadedVoxelMesh.getBuffer(),
dimensions: this._loadedVoxelMesh.getBounds().getDimensions(),
voxelSize: 8.0 / params.desiredHeight,
};
@ -73,25 +65,21 @@ export class WorkerClient {
public assign(params: AssignParams.Input): AssignParams.Output {
ASSERT(this._loadedVoxelMesh !== undefined);
this._loadedBlockMesh = BlockMesh.createFromVoxelMesh(this._loadedVoxelMesh, params);
return {
}
};
}
public renderBlockMesh(params: RenderBlockMeshParams.Input): RenderBlockMeshParams.Output {
ASSERT(this._loadedBlockMesh !== undefined);
ASSERT(this._voxelMeshBuffer !== undefined);
const atlas = Atlas.load(params.textureAtlas);
ASSERT(atlas !== undefined);
const buffer = BufferGenerator.fromBlockMesh(this._loadedBlockMesh, this._voxelMeshBuffer);
this._blockMeshBuffer = buffer.buffer;
return {
buffer: buffer,
buffer: this._loadedBlockMesh.getBuffer(),
dimensions: this._loadedBlockMesh.getVoxelMesh().getBounds().getDimensions(),
atlasTexturePath: atlas.getAtlasTexturePath(),
atlasSize: atlas.getAtlasSize(),
@ -100,16 +88,15 @@ export class WorkerClient {
public export(params: ExportParams.Input): ExportParams.Output {
ASSERT(this._loadedBlockMesh !== undefined);
ASSERT(this._blockMeshBuffer !== undefined);
const exporter: IExporter = ExporterFactory.GetExporter(params.exporter);
const fileExtension = '.' + exporter.getFileExtension();
if (!params.filepath.endsWith(fileExtension)) {
params.filepath += fileExtension;
}
exporter.export(this._loadedBlockMesh, params.filepath, this._blockMeshBuffer);
exporter.export(this._loadedBlockMesh, params.filepath);
return {
}
};
}
}
}

View File

@ -1,14 +1,14 @@
import { TBlockAssigners } from "./assigners/assigners"
import { FallableBehaviour } from "./block_mesh"
import { TBlockMeshBufferDescription, TMeshBufferDescription, TVoxelMeshBuffer, TVoxelMeshBufferDescription } from "./buffer"
import { TExporters } from "./exporters/exporters"
import { StatusMessage } from "./status"
import { TextureFiltering } from "./texture"
import { ColourSpace } from "./util"
import { AppError } from "./util/error_util"
import { Vector3 } from "./vector"
import { TVoxelisers } from "./voxelisers/voxelisers"
import { TVoxelOverlapRule } from "./voxel_mesh"
import { TBlockAssigners } from './assigners/assigners';
import { FallableBehaviour } from './block_mesh';
import { TBlockMeshBufferDescription, TMeshBufferDescription, TVoxelMeshBufferDescription } from './buffer';
import { TExporters } from './exporters/exporters';
import { StatusMessage } from './status';
import { TextureFiltering } from './texture';
import { ColourSpace } from './util';
import { AppError } from './util/error_util';
import { Vector3 } from './vector';
import { TVoxelisers } from './voxelisers/voxelisers';
import { TVoxelOverlapRule } from './voxel_mesh';
export namespace ImportParams {
export type Input = {
@ -32,7 +32,7 @@ export namespace RenderMeshParams {
}
export namespace VoxeliseParams {
export type Input = {
export type Input = {
voxeliser: TVoxelisers,
desiredHeight: number,
useMultisampleColouring: boolean,
@ -106,22 +106,22 @@ export type TStatus = {
}
export type TToWorkerMessage =
| { action: 'Import', params: ImportParams.Input }
| { action: 'RenderMesh', params: RenderMeshParams.Input }
| { action: 'Voxelise', params: VoxeliseParams.Input }
| { action: 'RenderVoxelMesh', params: RenderVoxelMeshParams.Input }
| { action: 'Assign', params: AssignParams.Input }
| { action: 'RenderBlockMesh', params: RenderBlockMeshParams.Input }
| { action: 'Export', params: ExportParams.Input }
| { action: 'Import', params: ImportParams.Input }
| { action: 'RenderMesh', params: RenderMeshParams.Input }
| { action: 'Voxelise', params: VoxeliseParams.Input }
| { action: 'RenderVoxelMesh', params: RenderVoxelMeshParams.Input }
| { action: 'Assign', params: AssignParams.Input }
| { action: 'RenderBlockMesh', params: RenderBlockMeshParams.Input }
| { action: 'Export', params: ExportParams.Input }
export type TFromWorkerMessage =
export type TFromWorkerMessage =
| { action: 'KnownError', error: AppError }
| { action: 'UnknownError', error: Error }
| (TStatus & (
| { action: 'Import', result: ImportParams.Output }
| { action: 'RenderMesh', result: RenderMeshParams.Output }
| { action: 'Voxelise', result: VoxeliseParams.Output }
| { action: 'RenderVoxelMesh', result: RenderVoxelMeshParams.Output }
| { action: 'Assign', result: AssignParams.Output }
| { action: 'RenderBlockMesh', result: RenderBlockMeshParams.Output }
| { action: 'Export', result: ExportParams.Output } ));
| (TStatus & (
| { action: 'Import', result: ImportParams.Output }
| { action: 'RenderMesh', result: RenderMeshParams.Output }
| { action: 'Voxelise', result: VoxeliseParams.Output }
| { action: 'RenderVoxelMesh', result: RenderVoxelMeshParams.Output }
| { action: 'Assign', result: AssignParams.Output }
| { action: 'RenderBlockMesh', result: RenderBlockMeshParams.Output }
| { action: 'Export', result: ExportParams.Output }));

View File

@ -1,4 +1,4 @@
import { AttributeData, MergeAttributeData } from "../src/render_buffer";
import { AttributeData, MergeAttributeData } from '../src/render_buffer';
test('MergeAttributeData #1', () => {
const a: AttributeData = {

View File

@ -1,34 +1,32 @@
/*
import { THeadlessConfig } from './headless';
import { TextureFiltering } from '../src/texture';
import { ColourSpace } from '../src/util';
export const headlessConfig: THeadlessConfig = {
import: {
absoluteFilePathLoad: 'C:/Users/<username>/Desktop/my_model.obj', // Must be an absolute path to the file (can be anywhere)
filepath: '/Users/lucasdower/ObjToSchematic/res/samples/skull.obj', // Must be an absolute path
},
voxelise: {
voxeliser: 'bvh-ray',
voxelMeshParams: {
desiredHeight: 80, // 5-320 inclusive
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Linear,
voxelOverlapRule: 'average',
},
desiredHeight: 80,
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Linear,
voxelOverlapRule: 'average',
enableAmbientOcclusion: false, // Only want true if exporting to .obj
calculateNeighbours: false, // Only want true if exporting to .obj
},
palette: {
blockMeshParams: {
textureAtlas: 'vanilla', // Must be an atlas name that exists in /resources/atlases
blockPalette: 'all-snapshot', // Must be a palette name that exists in /resources/palettes
blockAssigner: 'ordered-dithering',
colourSpace: ColourSpace.RGB,
fallable: 'replace-falling',
},
assign: {
textureAtlas: 'vanilla', // Must be an atlas name that exists in /resources/atlases
blockPalette: 'all-snapshot', // Must be a palette name that exists in /resources/palettes
blockAssigner: 'ordered-dithering',
colourSpace: ColourSpace.RGB,
fallable: 'replace-falling',
},
export: {
absoluteFilePathSave: 'C:/Users/Lucas/Desktop/my_structure.schematic', // Must be an absolute path to the file (can be anywhere)
exporter: 'schematic', // 'schematic' / 'litematic',
filepath: '/Users/lucasdower/Documents/out.obj', // Must be an absolute path to the file (can be anywhere)
exporter: 'obj', // 'schematic' / 'litematic',
},
debug: {
logging: true,
},
};
*/

View File

@ -1,118 +1,55 @@
/*
import { Mesh } from '../src/mesh';
import { ObjImporter } from '../src/importers/obj_importer';
import { IVoxeliser } from '../src/voxelisers/base-voxeliser';
import { TVoxelOverlapRule, VoxelMesh } from '../src/voxel_mesh';
import { BlockMesh, BlockMeshParams, FallableBehaviour } from '../src/block_mesh';
import { IExporter} from '../src/exporters/base_exporter';
import { TextureFiltering } from '../src/texture';
import { ColourSpace } from '../src/util';
import { log, LogStyle } from './logging';
import { headlessConfig } from './headless-config';
import { TVoxelisers, VoxeliserFactory } from '../src/voxelisers/voxelisers';
import { ExporterFactory, TExporters } from '../src/exporters/exporters';
import { TBlockAssigners } from '../src/assigners/assigners';
import { Atlas } from '../src/atlas';
import { Palette } from '../src/palette';
import { VoxeliseParams } from '../src/worker_types';
import { AssignParams, ExportParams, ImportParams, VoxeliseParams } from '../src/worker_types';
import { WorkerClient } from '../src/worker_client';
import { StatusHandler } from '../src/status';
import { Logger, LOG_MAJOR } from '../src/util/log_util';
export type THeadlessConfig = {
import: {
absoluteFilePathLoad: string,
},
voxelise: {
voxeliser: TVoxelisers,
voxelMeshParams: {
desiredHeight: number
useMultisampleColouring: boolean,
textureFiltering: TextureFiltering,
voxelOverlapRule: TVoxelOverlapRule,
},
},
palette: {
blockMeshParams: {
textureAtlas: string,
blockPalette: string,
blockAssigner: TBlockAssigners,
colourSpace: ColourSpace,
fallable: FallableBehaviour,
},
},
export: {
absoluteFilePathSave: string,
exporter: TExporters,
},
import: ImportParams.Input,
voxelise: VoxeliseParams.Input,
assign: AssignParams.Input,
export: ExportParams.Input,
debug: {
logging: boolean,
}
}
void async function main() {
const mesh = _import({
absoluteFilePathLoad: headlessConfig.import.absoluteFilePathLoad,
});
const voxelMesh = _voxelise(mesh, {
voxeliser: VoxeliserFactory.GetVoxeliser(headlessConfig.voxelise.voxeliser)
desiredHeight: headlessConfig.voxelise.voxelMeshParams.desiredHeight,
useMultisampleColouring: headlessConfig.voxelise.voxelMeshParams.useMultisampleColouring,
textureFiltering: headlessConfig.voxelise.voxelMeshParams.textureFiltering,
enableAmbientOcclusion: false,
voxelOverlapRule: headlessConfig.voxelise.voxelMeshParams.voxelOverlapRule,
calculateNeighbours: false,
});
const atlasId = headlessConfig.palette.blockMeshParams.textureAtlas;
const atlas = Atlas.load(atlasId);
if (atlas === undefined) {
return 'Could not load atlas';
if (headlessConfig.debug.logging) {
Logger.Get.enableLOGMAJOR();
}
const paletteId = headlessConfig.palette.blockMeshParams.blockPalette;
const palette = Palette.load(paletteId);
if (palette === undefined) {
return 'Could not load palette';
const worker = WorkerClient.Get;
{
LOG_MAJOR('Importing...');
worker.import(headlessConfig.import);
StatusHandler.Get.dump().clear();
}
const blockMesh = _palette(voxelMesh, {
blockMeshParams: {
textureAtlas: atlas,
blockPalette: palette,
blockAssigner: headlessConfig.palette.blockMeshParams.blockAssigner as TBlockAssigners,
colourSpace: headlessConfig.palette.blockMeshParams.colourSpace,
fallable: headlessConfig.palette.blockMeshParams.fallable as FallableBehaviour,
},
});
_export(blockMesh, {
absoluteFilePathSave: headlessConfig.export.absoluteFilePathSave,
exporter: ExporterFactory.GetExporter(headlessConfig.export.exporter),
});
log(LogStyle.Success, 'Finished!');
{
LOG_MAJOR('Voxelising...');
worker.voxelise(headlessConfig.voxelise);
StatusHandler.Get.dump().clear();
}
{
LOG_MAJOR('Assigning...');
worker.assign(headlessConfig.assign);
StatusHandler.Get.dump().clear();
}
{
LOG_MAJOR('Exporting...');
/**
* The OBJExporter is unique in that it uses the actual render buffer used by WebGL
* to create its data, in headless mode this render buffer is not created so we must
* generate it manually
*/
if (headlessConfig.export.exporter === 'obj') {
worker.renderVoxelMesh({
enableAmbientOcclusion: headlessConfig.voxelise.enableAmbientOcclusion,
desiredHeight: headlessConfig.voxelise.desiredHeight,
});
}
worker.export(headlessConfig.export);
StatusHandler.Get.dump().clear();
}
LOG_MAJOR('Finished!');
}();
// TODO: Log status messages
function _import(params: ImportParams): Mesh {
log(LogStyle.Info, 'Importing...');
const importer = new ObjImporter();
importer.parseFile(params.absoluteFilePathLoad);
const mesh = importer.toMesh();
mesh.processMesh();
return mesh;
}
// TODO: Log status messages
function _voxelise(mesh: Mesh, params: ActionVoxeliseParams): VoxelMesh {
log(LogStyle.Info, 'Voxelising...');
const voxeliser: IVoxeliser = params.voxeliser;
return voxeliser.voxelise(mesh, params.voxeliseParams);
}
// TODO: Log status messages
function _palette(voxelMesh: VoxelMesh, params: PaletteParams): BlockMesh {
log(LogStyle.Info, 'Assigning blocks...');
return BlockMesh.createFromVoxelMesh(voxelMesh, params.blockMeshParams);
}
// TODO: Log status messages
function _export(blockMesh: BlockMesh, params: ExportParams) {
log(LogStyle.Info, 'Exporting...');
params.exporter.export(blockMesh, params.absoluteFilePathSave);
}
*/

View File

@ -3,7 +3,7 @@
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
//"resolveJsonModule": true,