forked from mirror/ObjToSchematic
Remove AppContext and UI coupling
This commit is contained in:
parent
a99799d539
commit
b66a489af2
@ -3,11 +3,12 @@ import { Litematic, Schematic } from './schematic';
|
||||
import { Renderer } from './renderer';
|
||||
import { Mesh } from './mesh';
|
||||
import { ObjImporter } from './importers/obj_importer';
|
||||
import { ASSERT, CustomError, CustomWarning, LOG, LOG_ERROR, LOG_WARN } from './util';
|
||||
import { ASSERT, ColourSpace, CustomError, CustomWarning, LOG, LOG_ERROR, LOG_WARN } from './util';
|
||||
|
||||
import { remote } from 'electron';
|
||||
import { VoxelMesh } from './voxel_mesh';
|
||||
import { BlockMesh } from './block_mesh';
|
||||
import { VoxelMesh, VoxelMeshParams } from './voxel_mesh';
|
||||
import { BlockMesh, BlockMeshParams } from './block_mesh';
|
||||
import { TextureFiltering } from './texture';
|
||||
|
||||
/* eslint-disable */
|
||||
export enum ActionReturnType {
|
||||
@ -45,18 +46,14 @@ export class AppContext {
|
||||
private _loadedVoxelMesh?: VoxelMesh;
|
||||
private _loadedBlockMesh?: BlockMesh;
|
||||
private _warnings: string[];
|
||||
private _ui: UI;
|
||||
|
||||
private _actionMap = new Map<Action, {
|
||||
action: () => void;
|
||||
onFailure?: () => void
|
||||
}>();
|
||||
|
||||
private static _instance: AppContext;
|
||||
public static get Get() {
|
||||
return this._instance || (this._instance = new this());
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
public constructor() {
|
||||
const gl = (<HTMLCanvasElement>document.getElementById('canvas')).getContext('webgl');
|
||||
if (!gl) {
|
||||
throw Error('Could not load WebGL context');
|
||||
@ -98,29 +95,29 @@ export class AppContext {
|
||||
},
|
||||
});
|
||||
|
||||
UI.Get.build();
|
||||
UI.Get.registerEvents();
|
||||
|
||||
UI.Get.disable(Action.Simplify);
|
||||
this._ui = new UI(this);
|
||||
this._ui.build();
|
||||
this._ui.registerEvents();
|
||||
this._ui.disable(Action.Simplify);
|
||||
}
|
||||
|
||||
public do(action: Action) {
|
||||
UI.Get.disable(action + 1);
|
||||
this._ui.disable(action + 1);
|
||||
this._warnings = [];
|
||||
const groupName = UI.Get.uiOrder[action];
|
||||
const groupName = this._ui.uiOrder[action];
|
||||
LOG(`Doing ${action}`);
|
||||
UI.Get.cacheValues(action);
|
||||
this._ui.cacheValues(action);
|
||||
const delegate = this._actionMap.get(action)!;
|
||||
try {
|
||||
delegate.action();
|
||||
} catch (err: any) {
|
||||
LOG_ERROR(err);
|
||||
if (err instanceof CustomError) {
|
||||
UI.Get.layoutDull[groupName].output.setMessage(err.message, ActionReturnType.Failure);
|
||||
this._ui.layoutDull[groupName].output.setMessage(err.message, ActionReturnType.Failure);
|
||||
} else if (err instanceof CustomWarning) {
|
||||
UI.Get.layoutDull[groupName].output.setMessage(err.message, ActionReturnType.Warning);
|
||||
this._ui.layoutDull[groupName].output.setMessage(err.message, ActionReturnType.Warning);
|
||||
} else {
|
||||
UI.Get.layoutDull[groupName].output.setMessage(ReturnMessages.get(action)!.onFailure, ActionReturnType.Failure);
|
||||
this._ui.layoutDull[groupName].output.setMessage(ReturnMessages.get(action)!.onFailure, ActionReturnType.Failure);
|
||||
}
|
||||
if (delegate.onFailure) {
|
||||
delegate.onFailure();
|
||||
@ -131,17 +128,20 @@ export class AppContext {
|
||||
const successMessage = ReturnMessages.get(action)!.onSuccess;
|
||||
if (this._warnings.length !== 0) {
|
||||
const allWarnings = this._warnings.join('<br>');
|
||||
UI.Get.layoutDull[groupName].output.setMessage(successMessage + `, with ${this._warnings.length} warning(s):` + '<br><b>' + allWarnings + '</b>', ActionReturnType.Warning);
|
||||
this._ui.layoutDull[groupName].output.setMessage(successMessage + `, with ${this._warnings.length} warning(s):` + '<br><b>' + allWarnings + '</b>', ActionReturnType.Warning);
|
||||
} else {
|
||||
UI.Get.layoutDull[groupName].output.setMessage(successMessage, ActionReturnType.Success);
|
||||
this._ui.layoutDull[groupName].output.setMessage(successMessage, ActionReturnType.Success);
|
||||
}
|
||||
|
||||
LOG(`Finished ${action}`);
|
||||
UI.Get.enable(action + 1);
|
||||
this._ui.enable(action + 1);
|
||||
}
|
||||
|
||||
private _import() {
|
||||
this._loadedMesh = new ObjImporter().createMesh();
|
||||
const uiElements = this._ui.layout.import.elements;
|
||||
const filePath = uiElements.input.getCachedValue();
|
||||
|
||||
this._loadedMesh = new ObjImporter().createMesh(filePath);
|
||||
Renderer.Get.useMesh(this._loadedMesh);
|
||||
}
|
||||
|
||||
@ -151,22 +151,36 @@ export class AppContext {
|
||||
|
||||
private _voxelise() {
|
||||
ASSERT(this._loadedMesh);
|
||||
this._loadedVoxelMesh = new VoxelMesh();
|
||||
this._loadedVoxelMesh.voxelise(this._loadedMesh);
|
||||
|
||||
const uiElements = this._ui.layout.build.elements;
|
||||
const voxelMeshParams: VoxelMeshParams = {
|
||||
desiredHeight: uiElements.height.getCachedValue() as number,
|
||||
useMultisampleColouring: uiElements.multisampleColouring.getCachedValue() === 'on',
|
||||
textureFiltering: uiElements.textureFiltering.getCachedValue() === 'linear' ? TextureFiltering.Linear : TextureFiltering.Nearest,
|
||||
ambientOcclusionEnabled: uiElements.ambientOcclusion.getCachedValue() === 'on',
|
||||
};
|
||||
|
||||
this._loadedVoxelMesh = VoxelMesh.createFromMesh(this._loadedMesh, voxelMeshParams);
|
||||
Renderer.Get.useVoxelMesh(this._loadedVoxelMesh);
|
||||
}
|
||||
|
||||
private _palette() {
|
||||
ASSERT(this._loadedVoxelMesh);
|
||||
this._loadedBlockMesh = new BlockMesh();
|
||||
this._loadedBlockMesh.assignBlocks(this._loadedVoxelMesh);
|
||||
|
||||
const uiElements = this._ui.layout.palette.elements;
|
||||
const blockMeshParams: BlockMeshParams = {
|
||||
textureAtlas: uiElements.textureAtlas.getCachedValue(),
|
||||
blockPalette: uiElements.blockPalette.getCachedValue(),
|
||||
ditheringEnabled: uiElements.dithering.getCachedValue() === 'on',
|
||||
colourSpace: uiElements.colourSpace.getCachedValue() === 'rgb' ? ColourSpace.RGB : ColourSpace.LAB,
|
||||
};
|
||||
|
||||
this._loadedBlockMesh = BlockMesh.createFromVoxelMesh(this._loadedVoxelMesh, blockMeshParams);
|
||||
Renderer.Get.useBlockMesh(this._loadedBlockMesh);
|
||||
}
|
||||
|
||||
private _export() {
|
||||
const exportFormat = UI.Get.layout.export.elements.export.getCachedValue() as string;
|
||||
const exportFormat = this._ui.layout.export.elements.export.getCachedValue() as string;
|
||||
const exporter = (exportFormat === 'schematic') ? new Schematic() : new Litematic();
|
||||
|
||||
const filePath = remote.dialog.showSaveDialogSync({
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { BlockAtlas, BlockInfo } from './block_atlas';
|
||||
import { ASSERT, RGB } from './util';
|
||||
import { ASSERT, ColourSpace, RGB } from './util';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
interface BlockAssigner {
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3): BlockInfo;
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace): BlockInfo;
|
||||
}
|
||||
|
||||
export class BasicBlockAssigner implements BlockAssigner {
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3): BlockInfo {
|
||||
return BlockAtlas.Get.getBlock(voxelColour);
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace): BlockInfo {
|
||||
return BlockAtlas.Get.getBlock(voxelColour, colourSpace);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ export class OrderedDitheringBlockAssigner implements BlockAssigner {
|
||||
return (OrderedDitheringBlockAssigner._mapMatrix[index] / (size * size * size)) - 0.5;
|
||||
}
|
||||
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3): BlockInfo {
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3, colourSpace: ColourSpace): BlockInfo {
|
||||
const size = OrderedDitheringBlockAssigner._size;
|
||||
const map = this._getThresholdValue(
|
||||
Math.abs(voxelPosition.x % size),
|
||||
@ -50,6 +50,6 @@ export class OrderedDitheringBlockAssigner implements BlockAssigner {
|
||||
((255 * voxelColour.b) + map * OrderedDitheringBlockAssigner._threshold) / 255,
|
||||
);
|
||||
|
||||
return BlockAtlas.Get.getBlock(newVoxelColour);
|
||||
return BlockAtlas.Get.getBlock(newVoxelColour, colourSpace);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { HashMap } from './hash_map';
|
||||
import { UV, RGB, ASSERT, fileExists } from './util';
|
||||
import { UV, RGB, ASSERT, fileExists, ColourSpace } from './util';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
import fs from 'fs';
|
||||
@ -94,7 +94,7 @@ export class BlockAtlas {
|
||||
this._paletteLoaded = true;
|
||||
}
|
||||
|
||||
public getBlock(voxelColour: RGB): BlockInfo {
|
||||
public getBlock(voxelColour: RGB, colourSpace: ColourSpace): BlockInfo {
|
||||
ASSERT(this._atlasLoaded, 'No atlas has been loaded');
|
||||
ASSERT(this._paletteLoaded, 'No palette has been loaded');
|
||||
|
||||
@ -110,7 +110,7 @@ export class BlockAtlas {
|
||||
const block: BlockInfo = this._blocks[i];
|
||||
if (this._palette.includes(block.name)) {
|
||||
const blockAvgColour = block.colour as RGB;
|
||||
const distance = RGB.distance(blockAvgColour, voxelColour);
|
||||
const distance = RGB.distance(blockAvgColour, voxelColour, colourSpace);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
|
@ -1,43 +1,52 @@
|
||||
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from './block_assigner';
|
||||
import { Voxel, VoxelMesh } from './voxel_mesh';
|
||||
import { BlockAtlas, BlockInfo } from './block_atlas';
|
||||
import { CustomError, LOG } from './util';
|
||||
import { ColourSpace, CustomError, LOG } from './util';
|
||||
import { Renderer } from './renderer';
|
||||
import { UI } from './ui/layout';
|
||||
|
||||
interface Block {
|
||||
voxel: Voxel;
|
||||
blockInfo: BlockInfo;
|
||||
}
|
||||
|
||||
export interface BlockMeshParams {
|
||||
textureAtlas: string,
|
||||
blockPalette: string,
|
||||
ditheringEnabled: boolean,
|
||||
colourSpace: ColourSpace,
|
||||
}
|
||||
|
||||
export class BlockMesh {
|
||||
private _blockPalette: string[];
|
||||
private _blocks: Block[];
|
||||
private _voxelMesh?: VoxelMesh;
|
||||
private _voxelMesh: VoxelMesh;
|
||||
|
||||
public constructor() {
|
||||
public static createFromVoxelMesh(voxelMesh: VoxelMesh, blockMeshParams: BlockMeshParams) {
|
||||
const blockMesh = new BlockMesh(voxelMesh);
|
||||
blockMesh._assignBlocks(blockMeshParams);
|
||||
return blockMesh;
|
||||
}
|
||||
|
||||
private constructor(voxelMesh: VoxelMesh) {
|
||||
LOG('New block mesh');
|
||||
|
||||
this._blockPalette = [];
|
||||
this._blocks = [];
|
||||
this._voxelMesh = voxelMesh;
|
||||
}
|
||||
|
||||
public assignBlocks(voxelMesh: VoxelMesh) {
|
||||
private _assignBlocks(blockMeshParams: BlockMeshParams) {
|
||||
LOG('Assigning blocks');
|
||||
|
||||
const textureAtlas = UI.Get.layout.palette.elements.textureAtlas.getCachedValue() as string;
|
||||
BlockAtlas.Get.loadAtlas(textureAtlas);
|
||||
BlockAtlas.Get.loadAtlas(blockMeshParams.textureAtlas);
|
||||
BlockAtlas.Get.loadPalette(blockMeshParams.blockPalette);
|
||||
|
||||
const blockPalette = UI.Get.layout.palette.elements.blockPalette.getCachedValue() as string;
|
||||
BlockAtlas.Get.loadPalette(blockPalette);
|
||||
|
||||
const ditheringEnabled = UI.Get.layout.palette.elements.dithering.getCachedValue() as string === 'on';
|
||||
const blockAssigner = ditheringEnabled ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner();
|
||||
const blockAssigner = blockMeshParams.ditheringEnabled ? new OrderedDitheringBlockAssigner() : new BasicBlockAssigner();
|
||||
|
||||
const voxels = voxelMesh.getVoxels();
|
||||
const voxels = this._voxelMesh.getVoxels();
|
||||
for (let voxelIndex = 0; voxelIndex < voxels.length; ++voxelIndex) {
|
||||
const voxel = voxels[voxelIndex];
|
||||
const block = blockAssigner.assignBlock(voxel.colour, voxel.position);
|
||||
const block = blockAssigner.assignBlock(voxel.colour, voxel.position, blockMeshParams.colourSpace);
|
||||
|
||||
this._blocks.push({
|
||||
voxel: voxel,
|
||||
@ -47,8 +56,6 @@ export class BlockMesh {
|
||||
this._blockPalette.push(block.name);
|
||||
}
|
||||
}
|
||||
|
||||
this._voxelMesh = voxelMesh;
|
||||
}
|
||||
|
||||
public getBlocks(): Block[] {
|
||||
|
@ -24,7 +24,7 @@ addEvent('canvas', 'mousemove', (e) => {
|
||||
|
||||
|
||||
// Begin draw loop
|
||||
const context = AppContext.Get;
|
||||
const context = new AppContext();
|
||||
function render() {
|
||||
context.draw();
|
||||
requestAnimationFrame(render);
|
||||
|
@ -12,5 +12,5 @@ export namespace AppConfig {
|
||||
|
||||
export const DEBUG_ENABLED = true;
|
||||
|
||||
export const MULTISAMPLE_COUNT = 64;
|
||||
export const MULTISAMPLE_COUNT = 4;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Mesh } from './mesh';
|
||||
import { Warnable } from './util';
|
||||
|
||||
export abstract class IImporter {
|
||||
export abstract class IImporter extends Warnable {
|
||||
abstract createMesh(filePath: string): Mesh;
|
||||
}
|
||||
|
@ -2,12 +2,10 @@ import { IImporter } from '../importer';
|
||||
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
|
||||
import { Vector3 } from '../vector';
|
||||
import { UV, ASSERT, RGB, CustomError, LOG, REGEX_NUMBER, RegExpBuilder, REGEX_NZ_ANY, LOG_ERROR } from '../util';
|
||||
import { UI } from '../ui/layout';
|
||||
import { checkFractional, checkNaN } from '../math';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { AppContext } from '../app_context';
|
||||
|
||||
export class ObjImporter extends IImporter {
|
||||
private _vertices: Vector3[] = [];
|
||||
@ -196,8 +194,7 @@ export class ObjImporter extends IImporter {
|
||||
},
|
||||
];
|
||||
|
||||
override createMesh(): Mesh {
|
||||
const filePath = UI.Get.layout.import.elements.input.getCachedValue();
|
||||
override createMesh(filePath: string): Mesh {
|
||||
ASSERT(path.isAbsolute(filePath));
|
||||
|
||||
this._objPath = path.parse(filePath);
|
||||
@ -205,7 +202,7 @@ export class ObjImporter extends IImporter {
|
||||
this._parseOBJ(filePath);
|
||||
|
||||
if (this._mtlLibs.length === 0) {
|
||||
AppContext.Get.addWarning('Could not find associated .mtl file');
|
||||
this.addWarning('Could not find associated .mtl file');
|
||||
}
|
||||
for (let i = 0; i < this._mtlLibs.length; ++i) {
|
||||
const mtlLib = this._mtlLibs[i];
|
||||
|
12
src/mesh.ts
12
src/mesh.ts
@ -1,8 +1,7 @@
|
||||
import { Vector3 } from './vector';
|
||||
import { UV, Bounds, LOG, ASSERT, CustomError, LOG_WARN } from './util';
|
||||
import { UV, Bounds, LOG, ASSERT, CustomError, LOG_WARN, Warnable, getRandomID } from './util';
|
||||
import { UVTriangle } from './triangle';
|
||||
import { RGB } from './util';
|
||||
import { AppContext } from './app_context';
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
@ -24,21 +23,24 @@ export interface SolidMaterial { colour: RGB; type: MaterialType.solid }
|
||||
export interface TexturedMaterial { path: string; type: MaterialType.textured }
|
||||
export type MaterialMap = {[key: string]: (SolidMaterial | TexturedMaterial)};
|
||||
|
||||
export class Mesh {
|
||||
export class Mesh extends Warnable {
|
||||
public vertices!: Vector3[];
|
||||
public uvs!: UV[];
|
||||
public tris!: Tri[];
|
||||
public materials!: MaterialMap;
|
||||
public readonly id: string;
|
||||
|
||||
public static desiredHeight = 8.0;
|
||||
|
||||
constructor(vertices: Vector3[], uvs: UV[], tris: Tri[], materials: MaterialMap) {
|
||||
super();
|
||||
LOG('New mesh');
|
||||
|
||||
this.vertices = vertices;
|
||||
this.uvs = uvs;
|
||||
this.tris = tris;
|
||||
this.materials = materials;
|
||||
this.id = getRandomID();
|
||||
|
||||
this._checkMesh();
|
||||
this._checkMaterials();
|
||||
@ -101,7 +103,7 @@ export class Mesh {
|
||||
}
|
||||
if (wasRemapped) {
|
||||
LOG_WARN('Triangles use these materials but they were not found', missingMaterials);
|
||||
AppContext.Get.addWarning('Some materials were not loaded correctly');
|
||||
this.addWarning('Some materials were not loaded correctly');
|
||||
this.materials[debugName] = {
|
||||
type: MaterialType.solid,
|
||||
colour: RGB.white,
|
||||
@ -114,7 +116,7 @@ export class Mesh {
|
||||
if (material.type === MaterialType.textured) {
|
||||
ASSERT(path.isAbsolute(material.path), 'Material texture path not absolute');
|
||||
if (!fs.existsSync(material.path)) {
|
||||
AppContext.Get.addWarning(`Could not find ${material.path}`);
|
||||
this.addWarning(`Could not find ${material.path}`);
|
||||
LOG_WARN(`Could not find ${material.path} for material ${materialName}, changing to solid-white material`);
|
||||
this.materials[materialName] = {
|
||||
type: MaterialType.solid,
|
||||
|
@ -5,7 +5,7 @@ import { RenderBuffer } from './buffer';
|
||||
import { GeometryTemplates } from './geometry';
|
||||
import { Mesh, SolidMaterial, TexturedMaterial, MaterialType } from './mesh';
|
||||
import { BlockAtlas } from './block_atlas';
|
||||
import { LOG, RGB } from './util';
|
||||
import { ASSERT, LOG, RGB } from './util';
|
||||
import { VoxelMesh } from './voxel_mesh';
|
||||
import { BlockMesh } from './block_mesh';
|
||||
|
||||
|
@ -4,10 +4,9 @@ import path from 'path';
|
||||
import { NBT, TagType, writeUncompressed } from 'prismarine-nbt';
|
||||
import { Vector3 } from './vector';
|
||||
import { BlockMesh } from './block_mesh';
|
||||
import { LOG } from './util';
|
||||
import { AppContext } from './app_context';
|
||||
import { LOG, Warnable } from './util';
|
||||
|
||||
export abstract class Exporter {
|
||||
export abstract class Exporter extends Warnable {
|
||||
protected _sizeVector!: Vector3;
|
||||
|
||||
public abstract convertToNBT(blockMesh: BlockMesh): NBT
|
||||
@ -68,7 +67,7 @@ export class Schematic extends Exporter {
|
||||
}
|
||||
|
||||
if (unsupportedBlocks.size > 0) {
|
||||
AppContext.Get.addWarning(`${numBlocksUnsupported} blocks (${unsupportedBlocks.size} unique) are not supported by the .schematic format, Stone block are used in their place. Try using the schematic-friendly palette, or export using .litematica`);
|
||||
this.addWarning(`${numBlocksUnsupported} blocks (${unsupportedBlocks.size} unique) are not supported by the .schematic format, Stone block are used in their place. Try using the schematic-friendly palette, or export using .litematica`);
|
||||
LOG('Unsupported blocks', unsupportedBlocks);
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ASSERT } from '../../util';
|
||||
|
||||
export abstract class BaseUIElement {
|
||||
export abstract class BaseUIElement<Type> {
|
||||
protected _id: string;
|
||||
protected _label: string;
|
||||
protected _isEnabled: boolean;
|
||||
protected _value: any;
|
||||
protected _value?: Type;
|
||||
protected _cachedValue?: any;
|
||||
|
||||
constructor(label: string) {
|
||||
@ -18,12 +18,13 @@ export abstract class BaseUIElement {
|
||||
this._onEnabledChanged();
|
||||
}
|
||||
|
||||
public getCachedValue() {
|
||||
public getCachedValue(): Type {
|
||||
ASSERT(this._cachedValue !== undefined, 'Attempting to access value before cached');
|
||||
return this._cachedValue;
|
||||
return this._cachedValue as Type;
|
||||
}
|
||||
|
||||
protected getValue() {
|
||||
ASSERT(this._value);
|
||||
return this._value;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BaseUIElement } from './base';
|
||||
import { ASSERT } from '../../util';
|
||||
|
||||
export class ButtonElement extends BaseUIElement {
|
||||
export class ButtonElement extends BaseUIElement<any> {
|
||||
private _onClick: () => void;
|
||||
|
||||
public constructor(label: string, onClick: () => void) {
|
||||
|
@ -6,7 +6,7 @@ export interface ComboBoxItem {
|
||||
displayText: string;
|
||||
}
|
||||
|
||||
export class ComboBoxElement extends LabelledElement {
|
||||
export class ComboBoxElement extends LabelledElement<string> {
|
||||
private _items: ComboBoxItem[];
|
||||
|
||||
public constructor(id: string, items: ComboBoxItem[]) {
|
||||
|
@ -4,7 +4,7 @@ import { ASSERT } from '../../util';
|
||||
import { remote } from 'electron';
|
||||
import * as path from 'path';
|
||||
|
||||
export class FileInputElement extends LabelledElement {
|
||||
export class FileInputElement extends LabelledElement<string> {
|
||||
private _fileExtension: string;
|
||||
private _loadedFilePath: string;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BaseUIElement } from './base';
|
||||
import { LabelElement } from './label';
|
||||
|
||||
export abstract class LabelledElement extends BaseUIElement {
|
||||
export abstract class LabelledElement<Type> extends BaseUIElement<Type> {
|
||||
private _labelElement: LabelElement;
|
||||
|
||||
public constructor(label: string) {
|
||||
|
@ -2,7 +2,7 @@ import { ASSERT } from '../../util';
|
||||
import { clamp } from '../../math';
|
||||
import { LabelledElement } from './labelled_element';
|
||||
|
||||
export class SliderElement extends LabelledElement {
|
||||
export class SliderElement extends LabelledElement<number> {
|
||||
private _min: number;
|
||||
private _max: number;
|
||||
private _decimals: number;
|
||||
@ -18,7 +18,7 @@ export class SliderElement extends LabelledElement {
|
||||
}
|
||||
|
||||
public generateInnerHTML() {
|
||||
const norm = (this._value - this._min) / (this._max - this._min);
|
||||
const norm = (this.getValue() - this._min) / (this._max - this._min);
|
||||
return `
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
<div class="slider-value" id="${this._id + '-value'}">
|
||||
@ -76,10 +76,6 @@ export class SliderElement extends LabelledElement {
|
||||
elementValue.innerHTML = this._value.toFixed(this._decimals);
|
||||
}
|
||||
|
||||
protected getValue() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
protected _onEnabledChanged() {
|
||||
super._onEnabledChanged();
|
||||
|
||||
|
@ -12,7 +12,7 @@ import path from 'path';
|
||||
|
||||
export interface Group {
|
||||
label: string;
|
||||
elements: { [key: string]: BaseUIElement };
|
||||
elements: { [key: string]: BaseUIElement<any> };
|
||||
elementsOrder: string[];
|
||||
submitButton: ButtonElement;
|
||||
output: OutputElement;
|
||||
@ -28,7 +28,7 @@ export class UI {
|
||||
},
|
||||
elementsOrder: ['input'],
|
||||
submitButton: new ButtonElement('Load mesh', () => {
|
||||
AppContext.Get.do(Action.Import);
|
||||
this._appContext.do(Action.Import);
|
||||
}),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
@ -39,7 +39,7 @@ export class UI {
|
||||
},
|
||||
elementsOrder: ['ratio'],
|
||||
submitButton: new ButtonElement('Simplify mesh', () => {
|
||||
AppContext.Get.do(Action.Simplify);
|
||||
this._appContext.do(Action.Simplify);
|
||||
}),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
@ -62,7 +62,7 @@ export class UI {
|
||||
},
|
||||
elementsOrder: ['height', 'ambientOcclusion', 'multisampleColouring', 'textureFiltering'],
|
||||
submitButton: new ButtonElement('Voxelise mesh', () => {
|
||||
AppContext.Get.do(Action.Voxelise);
|
||||
this._appContext.do(Action.Voxelise);
|
||||
}),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
@ -82,7 +82,7 @@ export class UI {
|
||||
},
|
||||
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'colourSpace'],
|
||||
submitButton: new ButtonElement('Assign blocks', () => {
|
||||
AppContext.Get.do(Action.Palette);
|
||||
this._appContext.do(Action.Palette);
|
||||
}),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
@ -96,20 +96,16 @@ export class UI {
|
||||
},
|
||||
elementsOrder: ['export'],
|
||||
submitButton: new ButtonElement('Export structure', () => {
|
||||
AppContext.Get.do(Action.Export);
|
||||
this._appContext.do(Action.Export);
|
||||
}),
|
||||
output: new OutputElement(),
|
||||
},
|
||||
};
|
||||
private _uiDull: { [key: string]: Group } = this._ui;
|
||||
private _appContext: AppContext;
|
||||
|
||||
private static _instance: UI;
|
||||
public static get Get() {
|
||||
return this._instance || (this._instance = new this());
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
||||
constructor(appContext: AppContext) {
|
||||
this._appContext = appContext;
|
||||
}
|
||||
|
||||
public build() {
|
||||
@ -174,7 +170,7 @@ export class UI {
|
||||
`;
|
||||
}
|
||||
|
||||
private _buildSubcomponent(element: BaseUIElement) {
|
||||
private _buildSubcomponent(element: BaseUIElement<any>) {
|
||||
return `
|
||||
<div class="item item-body">
|
||||
${element.generateHTML()}
|
||||
|
35
src/util.ts
35
src/util.ts
@ -4,7 +4,6 @@ import { Vector3 } from './vector';
|
||||
const convert = require('color-convert');
|
||||
|
||||
import fs from 'fs';
|
||||
import { UI } from './ui/layout';
|
||||
|
||||
export class UV {
|
||||
public u: number;
|
||||
@ -15,6 +14,14 @@ export class UV {
|
||||
this.v = v;
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
export enum ColourSpace {
|
||||
RGB,
|
||||
LAB
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
export class RGB {
|
||||
public r: number;
|
||||
public g: number;
|
||||
@ -49,15 +56,15 @@ export class RGB {
|
||||
return [this.r, this.g, this.b];
|
||||
}
|
||||
|
||||
public static distance(a: RGB, b: RGB): number {
|
||||
const useLAB = UI.Get.layout.palette.elements.colourSpace.getCachedValue() === 'lab';
|
||||
if (useLAB) {
|
||||
public static distance(a: RGB, b: RGB, colourSpace: ColourSpace): number {
|
||||
if (colourSpace === ColourSpace.LAB) {
|
||||
const aLAB = convert.rgb.lab(a.r * 255, a.g * 255, a.b * 255);
|
||||
const bLAB = convert.rgb.lab(b.r * 255, b.g * 255, b.b * 255);
|
||||
const _a = Vector3.fromArray(aLAB);
|
||||
const _b = Vector3.fromArray(bLAB);
|
||||
return _a.sub(_b).magnitude();
|
||||
} else {
|
||||
ASSERT(colourSpace === ColourSpace.RGB);
|
||||
const _a = a.toVector3();
|
||||
const _b = b.toVector3();
|
||||
return _a.sub(_b).magnitude();
|
||||
@ -225,3 +232,23 @@ export class RegExpBuilder {
|
||||
return new RegExp(this._components.join(''));
|
||||
}
|
||||
}
|
||||
|
||||
export class Warnable {
|
||||
private _warnings: string[];
|
||||
|
||||
constructor() {
|
||||
this._warnings = [];
|
||||
}
|
||||
|
||||
public addWarning(warning: string) {
|
||||
this._warnings.push(warning);
|
||||
}
|
||||
|
||||
public getWarnings() {
|
||||
return this._warnings;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRandomID(): string {
|
||||
return (Math.random() + 1).toString(36).substring(7);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import { OcclusionManager } from './occlusion';
|
||||
import { Axes, generateRays, rayIntersectTriangle } from './ray';
|
||||
import { Texture, TextureFiltering } from './texture';
|
||||
import { Triangle, UVTriangle } from './triangle';
|
||||
import { UI } from './ui/layout';
|
||||
import { Bounds, LOG, RGB, UV } from './util';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
@ -15,20 +14,38 @@ export interface Voxel {
|
||||
position: Vector3;
|
||||
colour: RGB;
|
||||
collisions: number;
|
||||
|
||||
}
|
||||
export interface VoxelMeshParams {
|
||||
desiredHeight: number,
|
||||
useMultisampleColouring: boolean,
|
||||
textureFiltering: TextureFiltering,
|
||||
ambientOcclusionEnabled: boolean,
|
||||
}
|
||||
|
||||
export class VoxelMesh {
|
||||
private _mesh: Mesh;
|
||||
private _voxelMeshParams: VoxelMeshParams;
|
||||
|
||||
private _voxelSize: number;
|
||||
private _voxels: Voxel[];
|
||||
private _voxelsHash: HashMap<Vector3, number>;
|
||||
private _loadedTextures: { [materialName: string]: Texture };
|
||||
private _bounds: Bounds;
|
||||
|
||||
public constructor() {
|
||||
LOG('New voxel mesh');
|
||||
public static createFromMesh(mesh: Mesh, voxelMeshParams: VoxelMeshParams) {
|
||||
const voxelMesh = new VoxelMesh(mesh, voxelMeshParams);
|
||||
voxelMesh._voxelise();
|
||||
return voxelMesh;
|
||||
}
|
||||
|
||||
const desiredHeight = UI.Get.layout.build.elements.height.getCachedValue() as number;
|
||||
this._voxelSize = 8.0 / Math.round(desiredHeight);
|
||||
private constructor(mesh: Mesh, voxelMeshParams: VoxelMeshParams) {
|
||||
LOG('New voxel mesh');
|
||||
|
||||
this._mesh = mesh;
|
||||
this._voxelMeshParams = voxelMeshParams;
|
||||
|
||||
this._voxelSize = 8.0 / Math.round(voxelMeshParams.desiredHeight);
|
||||
this._voxels = [];
|
||||
this._voxelsHash = new HashMap(2048);
|
||||
this._loadedTextures = {};
|
||||
@ -43,17 +60,17 @@ export class VoxelMesh {
|
||||
return this._voxelsHash.has(pos);
|
||||
}
|
||||
|
||||
public voxelise(mesh: Mesh) {
|
||||
private _voxelise() {
|
||||
LOG('Voxelising');
|
||||
|
||||
mesh.tris.forEach((tri, index) => {
|
||||
const material = mesh.materials[tri.material];
|
||||
this._mesh.tris.forEach((tri, index) => {
|
||||
const material = this._mesh.materials[tri.material];
|
||||
if (material.type == MaterialType.textured) {
|
||||
if (!(tri.material in this._loadedTextures)) {
|
||||
this._loadedTextures[tri.material] = new Texture(material.path);
|
||||
}
|
||||
}
|
||||
const uvTriangle = mesh.getUVTriangle(index);
|
||||
const uvTriangle = this._mesh.getUVTriangle(index);
|
||||
this._voxeliseTri(uvTriangle, material, tri.material);
|
||||
});
|
||||
}
|
||||
@ -81,8 +98,7 @@ export class VoxelMesh {
|
||||
}
|
||||
|
||||
let voxelColour: RGB;
|
||||
const useMultisampleColouring = UI.Get.layout.build.elements.multisampleColouring.getCachedValue() as string === 'on';
|
||||
if (useMultisampleColouring && material.type === MaterialType.textured) {
|
||||
if (this._voxelMeshParams.useMultisampleColouring && material.type === MaterialType.textured) {
|
||||
const samples: RGB[] = [];
|
||||
for (let i = 0; i < AppConfig.MULTISAMPLE_COUNT; ++i) {
|
||||
const samplePosition = Vector3.mulScalar(Vector3.add(voxelPosition, Vector3.random().addScalar(-0.5)), this._voxelSize);
|
||||
@ -116,8 +132,7 @@ export class VoxelMesh {
|
||||
triangle.uv0.v * w0 + triangle.uv1.v * w1 + triangle.uv2.v * w2,
|
||||
);
|
||||
|
||||
const filtering = UI.Get.layout.build.elements.textureFiltering.getCachedValue() as string === 'linear' ? TextureFiltering.Linear : TextureFiltering.Nearest;
|
||||
return this._loadedTextures[materialName].getRGB(uv, filtering);
|
||||
return this._loadedTextures[materialName].getRGB(uv, this._voxelMeshParams.textureFiltering);
|
||||
}
|
||||
|
||||
private _addVoxel(pos: Vector3, colour: RGB) {
|
||||
@ -160,12 +175,11 @@ export class VoxelMesh {
|
||||
{ name: 'normal', numComponents: 3 },
|
||||
]);
|
||||
|
||||
const ambientOcclusionEnabled = UI.Get.layout.build.elements.ambientOcclusion.getCachedValue() as string === 'on';
|
||||
for (const voxel of this._voxels) {
|
||||
// Each vertex of a face needs the occlusion data for the other 3 vertices
|
||||
// in it's face, not just itself. Also flatten occlusion data.
|
||||
let occlusions: number[];
|
||||
if (ambientOcclusionEnabled) {
|
||||
if (this._voxelMeshParams.ambientOcclusionEnabled) {
|
||||
occlusions = OcclusionManager.Get.getOcclusions(voxel.position, this);
|
||||
} else {
|
||||
occlusions = OcclusionManager.Get.getBlankOcclusions();
|
||||
|
Loading…
Reference in New Issue
Block a user