forked from mirror/ObjToSchematic
Added linting
This commit is contained in:
parent
14d2311820
commit
a1065572a8
28
.eslintrc.json
Normal file
28
.eslintrc.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"google"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"linebreak-style": "off",
|
||||
"object-curly-spacing": "off",
|
||||
"max-len": "off",
|
||||
"require-jsdoc": "off",
|
||||
"indent": ["error", 4],
|
||||
"no-multi-spaces": "off",
|
||||
"no-array-constructor": "off",
|
||||
"guard-for-in": "off",
|
||||
"func-call-spacing": "off"
|
||||
}
|
||||
}
|
@ -7,7 +7,8 @@
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"lint": "eslint --fix ./src/*.ts",
|
||||
"build": "npm run lint && tsc",
|
||||
"start": "npm run build && electron ./dist/main.js --enable-logging",
|
||||
"atlas": "npx ts-node tools/build-atlas.ts",
|
||||
"export": "electron-packager . ObjToSchematic --platform=win32 --arch=x64"
|
||||
@ -26,10 +27,13 @@
|
||||
"@types/jquery": "^3.5.6",
|
||||
"@types/pngjs": "^6.0.1",
|
||||
"@types/prompt": "^1.1.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"adm-zip": "^0.5.9",
|
||||
"chalk": "^4.1.2",
|
||||
"electron": "^13.1.4",
|
||||
"electron-packager": "^15.2.0",
|
||||
"eslint": "^8.7.0",
|
||||
"images": "^3.2.3",
|
||||
"prompt": "^1.2.1",
|
||||
"ts-node": "^10.1.0",
|
||||
@ -37,6 +41,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"expand-vertex-data": "^1.1.2",
|
||||
"jpeg-js": "^0.4.3",
|
||||
"pngjs": "^6.0.0",
|
||||
|
@ -1,30 +1,33 @@
|
||||
import { buildUI, registerUI, Group, Component, setEnabled } from "./ui/layout";
|
||||
import { Exporter, Litematic, Schematic } from "./schematic";
|
||||
import { VoxelManager } from "./voxel_manager";
|
||||
import { Renderer } from "./renderer";
|
||||
import { Mesh } from "./mesh";
|
||||
import { buildUI, registerUI, Group, setEnabled } from './ui/layout';
|
||||
import { Exporter, Litematic, Schematic } from './schematic';
|
||||
import { VoxelManager } from './voxel_manager';
|
||||
import { Renderer } from './renderer';
|
||||
import { Mesh } from './mesh';
|
||||
|
||||
import { SliderElement } from "./ui/elements/slider";
|
||||
import { ComboBoxElement } from "./ui/elements/combobox";
|
||||
import { FileInputElement } from "./ui/elements/file_input";
|
||||
import { SliderElement } from './ui/elements/slider';
|
||||
import { ComboBoxElement } from './ui/elements/combobox';
|
||||
import { FileInputElement } from './ui/elements/file_input';
|
||||
|
||||
import { remote } from "electron";
|
||||
import fs from "fs";
|
||||
import { ButtonElement } from "./ui/elements/button";
|
||||
import { LabelElement } from "./ui/elements/label";
|
||||
import { OutputElement } from "./ui/elements/output";
|
||||
import { remote } from 'electron';
|
||||
import fs from 'fs';
|
||||
import { ButtonElement } from './ui/elements/button';
|
||||
import { LabelElement } from './ui/elements/label';
|
||||
import { OutputElement } from './ui/elements/output';
|
||||
|
||||
/* eslint-disable */
|
||||
export enum ActionReturnType {
|
||||
Success,
|
||||
Warning,
|
||||
Failure
|
||||
}
|
||||
/* eslint-enable */
|
||||
interface ReturnStatus {
|
||||
message: string,
|
||||
type: ActionReturnType,
|
||||
error?: unknown
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
export enum Action {
|
||||
Load = 0,
|
||||
Simplify = 1,
|
||||
@ -32,6 +35,7 @@ export enum Action {
|
||||
Palette = 3,
|
||||
Export = 4,
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
export class AppContext {
|
||||
public ambientOcclusion: boolean;
|
||||
@ -40,7 +44,7 @@ export class AppContext {
|
||||
private _gl: WebGLRenderingContext;
|
||||
private _loadedMesh?: Mesh;
|
||||
private _ui: Group[];
|
||||
private _actionMap = new Map<Action, (() => ReturnStatus | void)>();
|
||||
private _actionMap = new Map<Action, () => (ReturnStatus | void)>();
|
||||
|
||||
private static _instance: AppContext;
|
||||
|
||||
@ -52,98 +56,114 @@ export class AppContext {
|
||||
this.ambientOcclusion = true;
|
||||
this.dithering = true;
|
||||
|
||||
this._actionMap.set(Action.Load, () => { return this._load(); });
|
||||
this._actionMap.set(Action.Load, () => {
|
||||
return this._load();
|
||||
});
|
||||
this._actionMap.set(Action.Simplify, () => {});
|
||||
this._actionMap.set(Action.Voxelise, () => { return this._voxelise(); });
|
||||
this._actionMap.set(Action.Palette, () => { return this._palette(); } );
|
||||
this._actionMap.set(Action.Export, () => { return this._export(); });
|
||||
this._actionMap.set(Action.Voxelise, () => {
|
||||
return this._voxelise();
|
||||
});
|
||||
this._actionMap.set(Action.Palette, () => {
|
||||
return this._palette();
|
||||
} );
|
||||
this._actionMap.set(Action.Export, () => {
|
||||
return this._export();
|
||||
});
|
||||
|
||||
this._ui = [
|
||||
{
|
||||
label: "Input",
|
||||
label: 'Input',
|
||||
components: [
|
||||
{
|
||||
label: new LabelElement("label1", "Wavefront .obj file"),
|
||||
type: new FileInputElement("objFile", "obj")
|
||||
{
|
||||
label: new LabelElement('label1', 'Wavefront .obj file'),
|
||||
type: new FileInputElement('objFile', 'obj'),
|
||||
},
|
||||
{
|
||||
label: new LabelElement("label2", "Material .mtl file"),
|
||||
type: new FileInputElement("mtlFile", "mtl")
|
||||
{
|
||||
label: new LabelElement('label2', 'Material .mtl file'),
|
||||
type: new FileInputElement('mtlFile', 'mtl'),
|
||||
},
|
||||
],
|
||||
submitButton: new ButtonElement("loadMesh", "Load mesh", () => { this.do(Action.Load); }),
|
||||
output: new OutputElement("output1")
|
||||
submitButton: new ButtonElement('loadMesh', 'Load mesh', () => {
|
||||
this.do(Action.Load);
|
||||
}),
|
||||
output: new OutputElement('output1'),
|
||||
},
|
||||
{
|
||||
label: "Simplify",
|
||||
label: 'Simplify',
|
||||
components: [
|
||||
{
|
||||
label: new LabelElement("label3", "Ratio"),
|
||||
type: new SliderElement("ratio", 0.0, 1.0, 0.01, 0.5) },
|
||||
label: new LabelElement('label3', 'Ratio'),
|
||||
type: new SliderElement('ratio', 0.0, 1.0, 0.01, 0.5) },
|
||||
],
|
||||
submitButton: new ButtonElement("simplifyMesh", "Simplify mesh", () => { }),
|
||||
output: new OutputElement("output2")
|
||||
submitButton: new ButtonElement('simplifyMesh', 'Simplify mesh', () => { }),
|
||||
output: new OutputElement('output2'),
|
||||
},
|
||||
{
|
||||
label: "Build",
|
||||
label: 'Build',
|
||||
components: [
|
||||
{
|
||||
label: new LabelElement("label4", "Voxel size"),
|
||||
type: new SliderElement("voxelSize", 0.01, 0.5, 0.01, 0.1)
|
||||
label: new LabelElement('label4', 'Voxel size'),
|
||||
type: new SliderElement('voxelSize', 0.01, 0.5, 0.01, 0.1),
|
||||
},
|
||||
{
|
||||
label: new LabelElement("label5", "Ambient occlusion"),
|
||||
type: new ComboBoxElement("ambientOcclusion", [
|
||||
{ id: "on", displayText: "On (recommended)" },
|
||||
{ id: "off", displayText: "Off (faster)" },
|
||||
])
|
||||
label: new LabelElement('label5', 'Ambient occlusion'),
|
||||
type: new ComboBoxElement('ambientOcclusion', [
|
||||
{ id: 'on', displayText: 'On (recommended)' },
|
||||
{ id: 'off', displayText: 'Off (faster)' },
|
||||
]),
|
||||
},
|
||||
],
|
||||
submitButton: new ButtonElement("voxeliseMesh", "Voxelise mesh", () => { this.do(Action.Voxelise); }),
|
||||
output: new OutputElement("output3")
|
||||
submitButton: new ButtonElement('voxeliseMesh', 'Voxelise mesh', () => {
|
||||
this.do(Action.Voxelise);
|
||||
}),
|
||||
output: new OutputElement('output3'),
|
||||
},
|
||||
{
|
||||
label: "Palette",
|
||||
label: 'Palette',
|
||||
components: [
|
||||
{
|
||||
label: new LabelElement("label6", "Block palette"),
|
||||
type: new ComboBoxElement("blockPalette", [
|
||||
{ id: "default", displayText: "Default" }
|
||||
])
|
||||
label: new LabelElement('label6', 'Block palette'),
|
||||
type: new ComboBoxElement('blockPalette', [
|
||||
{ id: 'default', displayText: 'Default' },
|
||||
]),
|
||||
},
|
||||
{
|
||||
label: new LabelElement("label7", "Choice method"),
|
||||
type: new ComboBoxElement("choiceMethod", [
|
||||
{ id: "euclidian", displayText: "Euclidian distance" }
|
||||
])
|
||||
label: new LabelElement('label7', 'Choice method'),
|
||||
type: new ComboBoxElement('choiceMethod', [
|
||||
{ id: 'euclidian', displayText: 'Euclidian distance' },
|
||||
]),
|
||||
},
|
||||
{
|
||||
label: new LabelElement("label8", "Dithering"),
|
||||
type: new ComboBoxElement("dithering", [
|
||||
{ id: "on", displayText: "On (recommended)" },
|
||||
{ id: "off", displayText: "Off" },
|
||||
])
|
||||
label: new LabelElement('label8', 'Dithering'),
|
||||
type: new ComboBoxElement('dithering', [
|
||||
{ id: 'on', displayText: 'On (recommended)' },
|
||||
{ id: 'off', displayText: 'Off' },
|
||||
]),
|
||||
},
|
||||
],
|
||||
submitButton: new ButtonElement("assignBlocks", "Assign blocks", () => { this.do(Action.Palette); }),
|
||||
output: new OutputElement("output4")
|
||||
submitButton: new ButtonElement('assignBlocks', 'Assign blocks', () => {
|
||||
this.do(Action.Palette);
|
||||
}),
|
||||
output: new OutputElement('output4'),
|
||||
},
|
||||
{
|
||||
label: "Export",
|
||||
label: 'Export',
|
||||
components: [
|
||||
{
|
||||
label: new LabelElement("label9", "File format"),
|
||||
type: new ComboBoxElement("fileFormat",
|
||||
[
|
||||
{ id: "litematic", displayText: "Litematic" },
|
||||
{ id: "schematic", displayText: "Schematic" },
|
||||
])
|
||||
{
|
||||
label: new LabelElement('label9', 'File format'),
|
||||
type: new ComboBoxElement('fileFormat',
|
||||
[
|
||||
{ id: 'litematic', displayText: 'Litematic' },
|
||||
{ id: 'schematic', displayText: 'Schematic' },
|
||||
]),
|
||||
},
|
||||
],
|
||||
submitButton: new ButtonElement("exportStructure", "Export structure", () => { this.do(Action.Export); }),
|
||||
output: new OutputElement("output5")
|
||||
}
|
||||
]
|
||||
submitButton: new ButtonElement('exportStructure', 'Export structure', () => {
|
||||
this.do(Action.Export);
|
||||
}),
|
||||
output: new OutputElement('output5'),
|
||||
},
|
||||
];
|
||||
|
||||
buildUI(this._ui);
|
||||
registerUI(this._ui);
|
||||
@ -152,9 +172,9 @@ export class AppContext {
|
||||
setEnabled(this._ui[3], false);
|
||||
setEnabled(this._ui[4], false);
|
||||
|
||||
const gl = (<HTMLCanvasElement>document.getElementById('canvas')).getContext("webgl");
|
||||
const gl = (<HTMLCanvasElement>document.getElementById('canvas')).getContext('webgl');
|
||||
if (!gl) {
|
||||
throw Error("Could not load WebGL context");
|
||||
throw Error('Could not load WebGL context');
|
||||
}
|
||||
this._gl = gl;
|
||||
}
|
||||
@ -175,21 +195,21 @@ export class AppContext {
|
||||
setEnabled(this._ui[3], false);
|
||||
setEnabled(this._ui[4], false);
|
||||
|
||||
const objPath = (<FileInputElement>this._ui[0].components[0].type).getValue();
|
||||
const objPath = (<FileInputElement> this._ui[0].components[0].type).getValue();
|
||||
if (!fs.existsSync(objPath)) {
|
||||
return { message: "Selected .obj cannot be found", type: ActionReturnType.Failure };
|
||||
return { message: 'Selected .obj cannot be found', type: ActionReturnType.Failure };
|
||||
}
|
||||
|
||||
const mtlPath = (<FileInputElement>this._ui[0].components[1].type).getValue();
|
||||
const mtlPath = (<FileInputElement> this._ui[0].components[1].type).getValue();
|
||||
if (!fs.existsSync(mtlPath)) {
|
||||
return { message: "Selected .mtl cannot be found", type: ActionReturnType.Failure };
|
||||
return { message: 'Selected .mtl cannot be found', type: ActionReturnType.Failure };
|
||||
}
|
||||
|
||||
try {
|
||||
this._loadedMesh = new Mesh(objPath, this._gl);
|
||||
this._loadedMesh.loadTextures(this._gl);
|
||||
} catch (err: unknown) {
|
||||
return { error: err, message: "Could not load mesh", type: ActionReturnType.Failure };
|
||||
return { error: err, message: 'Could not load mesh', type: ActionReturnType.Failure };
|
||||
}
|
||||
|
||||
try {
|
||||
@ -198,20 +218,20 @@ export class AppContext {
|
||||
renderer.registerMesh(this._loadedMesh);
|
||||
renderer.compile();
|
||||
} catch (err: unknown) {
|
||||
return { message: "Could not render mesh", type: ActionReturnType.Failure };
|
||||
return { message: 'Could not render mesh', type: ActionReturnType.Failure };
|
||||
}
|
||||
|
||||
setEnabled(this._ui[2], true);
|
||||
return { message: "Loaded successfully", type: ActionReturnType.Success };
|
||||
return { message: 'Loaded successfully', type: ActionReturnType.Success };
|
||||
}
|
||||
|
||||
private _voxelise(): ReturnStatus {
|
||||
setEnabled(this._ui[3], false);
|
||||
setEnabled(this._ui[4], false);
|
||||
|
||||
const voxelSize = (<SliderElement>this._ui[2].components[0].type).getValue();
|
||||
const ambientOcclusion = (<ComboBoxElement>this._ui[2].components[1].type).getValue();
|
||||
this.ambientOcclusion = ambientOcclusion === "on";
|
||||
const voxelSize = (<SliderElement> this._ui[2].components[0].type).getValue();
|
||||
const ambientOcclusion = (<ComboBoxElement> this._ui[2].components[1].type).getValue();
|
||||
this.ambientOcclusion = ambientOcclusion === 'on';
|
||||
|
||||
try {
|
||||
const voxelManager = VoxelManager.Get;
|
||||
@ -223,18 +243,18 @@ export class AppContext {
|
||||
renderer.registerVoxelMesh();
|
||||
renderer.compile();
|
||||
} catch (err: any) {
|
||||
return { error: err, message: "Could not register voxel mesh", type: ActionReturnType.Failure };
|
||||
return { error: err, message: 'Could not register voxel mesh', type: ActionReturnType.Failure };
|
||||
}
|
||||
|
||||
setEnabled(this._ui[3], true);
|
||||
return { message: "Voxelised successfully", type: ActionReturnType.Success };
|
||||
return { message: 'Voxelised successfully', type: ActionReturnType.Success };
|
||||
}
|
||||
|
||||
private _palette(): ReturnStatus {
|
||||
setEnabled(this._ui[4], false);
|
||||
|
||||
const dithering = (<ComboBoxElement>this._ui[3].components[2].type).getValue();
|
||||
this.dithering = dithering === "on";
|
||||
|
||||
const dithering = (<ComboBoxElement> this._ui[3].components[2].type).getValue();
|
||||
this.dithering = dithering === 'on';
|
||||
|
||||
try {
|
||||
const voxelManager = VoxelManager.Get;
|
||||
@ -245,42 +265,42 @@ export class AppContext {
|
||||
renderer.registerVoxelMesh();
|
||||
renderer.compile();
|
||||
} catch (err: any) {
|
||||
return { error: err, message: "Could not register voxel mesh", type: ActionReturnType.Failure };
|
||||
return { error: err, message: 'Could not register voxel mesh', type: ActionReturnType.Failure };
|
||||
}
|
||||
|
||||
setEnabled(this._ui[4], true);
|
||||
return { message: "Blocks assigned successfully", type: ActionReturnType.Success };
|
||||
return { message: 'Blocks assigned successfully', type: ActionReturnType.Success };
|
||||
}
|
||||
|
||||
private _export(): ReturnStatus {
|
||||
const exportFormat = (<ComboBoxElement>this._ui[4].components[0].type).getValue();
|
||||
const exportFormat = (<ComboBoxElement> this._ui[4].components[0].type).getValue();
|
||||
let exporter: Exporter;
|
||||
if (exportFormat === "schematic") {
|
||||
if (exportFormat === 'schematic') {
|
||||
exporter = new Schematic();
|
||||
} else {
|
||||
exporter = new Litematic();
|
||||
}
|
||||
|
||||
const filePath = remote.dialog.showSaveDialogSync({
|
||||
title: "Save structure",
|
||||
buttonLabel: "Save",
|
||||
filters: [exporter.getFormatFilter()]
|
||||
title: 'Save structure',
|
||||
buttonLabel: 'Save',
|
||||
filters: [exporter.getFormatFilter()],
|
||||
});
|
||||
|
||||
if (filePath === undefined) {
|
||||
return { message: "Output cancelled", type: ActionReturnType.Warning };
|
||||
return { message: 'Output cancelled', type: ActionReturnType.Warning };
|
||||
}
|
||||
|
||||
try {
|
||||
exporter.export(filePath);
|
||||
} catch (err: unknown) {
|
||||
return { error: err, message: "Failed to export", type: ActionReturnType.Failure };
|
||||
return { error: err, message: 'Failed to export', type: ActionReturnType.Failure };
|
||||
}
|
||||
|
||||
return { message: "Successfully exported", type: ActionReturnType.Success };
|
||||
return { message: 'Successfully exported', type: ActionReturnType.Success };
|
||||
}
|
||||
|
||||
public draw() {
|
||||
Renderer.Get.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BlockAtlas, BlockInfo } from "./block_atlas";
|
||||
import { assert, RGB } from "./util";
|
||||
import { Vector3 } from "./vector";
|
||||
import { BlockAtlas, BlockInfo } from './block_atlas';
|
||||
import { assert, RGB } from './util';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
interface BlockAssigner {
|
||||
assignBlock(voxelColour: RGB, voxelPosition: Vector3): BlockInfo;
|
||||
@ -13,7 +13,6 @@ export class BasicBlockAssigner implements BlockAssigner {
|
||||
}
|
||||
|
||||
export class OrderedDitheringBlockAssigner implements BlockAssigner {
|
||||
|
||||
/** 4x4x4 */
|
||||
private static _size = 4;
|
||||
private static _threshold = 256 / 8;
|
||||
@ -26,12 +25,12 @@ export class OrderedDitheringBlockAssigner implements BlockAssigner {
|
||||
3, 19, 5, 21, 51, 35, 53, 37,
|
||||
1, 17, 7, 23, 49, 33, 55, 39,
|
||||
27, 43, 29, 45, 11, 59, 13, 61,
|
||||
25, 41, 31, 47, 9, 57, 15, 63
|
||||
25, 41, 31, 47, 9, 57, 15, 63,
|
||||
];
|
||||
|
||||
private _getThresholdValue(x: number, y: number, z: number) {
|
||||
const size = OrderedDitheringBlockAssigner._size;
|
||||
assert(0 <= x && x < size && 0 <= y && y < size && 0 <= z && z < size)
|
||||
assert(0 <= x && x < size && 0 <= y && y < size && 0 <= z && z < size);
|
||||
const index = (x + (size * y) + (size * size * z));
|
||||
assert(0 <= index && index < size * size * size);
|
||||
return (OrderedDitheringBlockAssigner._mapMatrix[index] / (size * size * size)) - 0.5;
|
||||
@ -42,16 +41,15 @@ export class OrderedDitheringBlockAssigner implements BlockAssigner {
|
||||
const map = this._getThresholdValue(
|
||||
Math.abs(voxelPosition.x % size),
|
||||
Math.abs(voxelPosition.y % size),
|
||||
Math.abs(voxelPosition.z % size)
|
||||
Math.abs(voxelPosition.z % size),
|
||||
);
|
||||
|
||||
const newVoxelColour = {
|
||||
r: ((255 * voxelColour.r) + map * OrderedDitheringBlockAssigner._threshold) / 255,
|
||||
g: ((255 * voxelColour.g) + map * OrderedDitheringBlockAssigner._threshold) / 255,
|
||||
b: ((255 * voxelColour.b) + map * OrderedDitheringBlockAssigner._threshold) / 255
|
||||
b: ((255 * voxelColour.b) + map * OrderedDitheringBlockAssigner._threshold) / 255,
|
||||
};
|
||||
|
||||
return BlockAtlas.Get.getBlock(newVoxelColour);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { Vector3 } from "./vector";
|
||||
import { HashMap } from "./hash_map";
|
||||
import { UV, RGB } from "./util";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import { Vector3 } from './vector';
|
||||
import { HashMap } from './hash_map';
|
||||
import { UV, RGB } from './util';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export interface TextureInfo {
|
||||
name: string
|
||||
@ -27,14 +26,14 @@ export interface BlockInfo {
|
||||
}
|
||||
|
||||
// https://minecraft.fandom.com/wiki/Java_Edition_data_values/Pre-flattening/Block_IDs
|
||||
/* eslint-disable */
|
||||
export enum Block {
|
||||
Stone = 1.0,
|
||||
Dirt = 3.0,
|
||||
Cobblestone = 4.0
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
export class BlockAtlas {
|
||||
|
||||
private _cachedBlocks: HashMap<Vector3, number>;
|
||||
private readonly _blocks: Array<BlockInfo>;
|
||||
public readonly _atlasSize: number;
|
||||
@ -48,13 +47,13 @@ export class BlockAtlas {
|
||||
private constructor() {
|
||||
this._cachedBlocks = new HashMap(1024);
|
||||
|
||||
const _path = path.join(__dirname, "../resources/blocks.json");
|
||||
const blocksString = fs.readFileSync(_path, "utf-8");
|
||||
const _path = path.join(__dirname, '../resources/blocks.json');
|
||||
const blocksString = fs.readFileSync(_path, 'utf-8');
|
||||
if (!blocksString) {
|
||||
throw Error("Could not load blocks.json")
|
||||
throw Error('Could not load blocks.json');
|
||||
}
|
||||
|
||||
const json = JSON.parse(blocksString)
|
||||
|
||||
const json = JSON.parse(blocksString);
|
||||
this._atlasSize = json.atlasSize;
|
||||
this._blocks = json.blocks;
|
||||
}
|
||||
@ -63,7 +62,7 @@ export class BlockAtlas {
|
||||
public getBlock(voxelColour: RGB): BlockInfo {
|
||||
const voxelColourVector = new Vector3(voxelColour.r, voxelColour.g, voxelColour.b);
|
||||
|
||||
let cachedBlockIndex = this._cachedBlocks.get(voxelColourVector);
|
||||
const cachedBlockIndex = this._cachedBlocks.get(voxelColourVector);
|
||||
if (cachedBlockIndex) {
|
||||
return this._blocks[cachedBlockIndex];
|
||||
}
|
||||
@ -77,7 +76,7 @@ export class BlockAtlas {
|
||||
const blockAvgColourVector = new Vector3(
|
||||
blockAvgColour.r,
|
||||
blockAvgColour.g,
|
||||
blockAvgColour.b
|
||||
blockAvgColour.b,
|
||||
);
|
||||
|
||||
const distance = Vector3.sub(blockAvgColourVector, voxelColourVector).magnitude();
|
||||
@ -90,5 +89,4 @@ export class BlockAtlas {
|
||||
this._cachedBlocks.add(voxelColourVector, blockChoiceIndex);
|
||||
return this._blocks[blockChoiceIndex];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import * as twgl from "twgl.js";
|
||||
import * as twgl from 'twgl.js';
|
||||
|
||||
/*
|
||||
WebGL buffers store vertex index data as Uint16 and will lead to frequent overflows.
|
||||
SegmentedBuffer automatically partitions buffers to avoid overflows and removes the
|
||||
SegmentedBuffer automatically partitions buffers to avoid overflows and removes the
|
||||
overhead of .push/.concat when adding data
|
||||
*/
|
||||
|
||||
@ -46,16 +46,15 @@ export interface VoxelData {
|
||||
}
|
||||
|
||||
export class SegmentedBuffer {
|
||||
|
||||
public WebGLBuffers: Array<{
|
||||
buffer: twgl.BufferInfo,
|
||||
numElements: number
|
||||
}>
|
||||
}>;
|
||||
|
||||
private _bufferSize: number;
|
||||
private _completeBuffers: Array<CompleteBuffer>;
|
||||
private _buffer!: SegmentedBufferData;
|
||||
private _attributes: {[name: string]: IndexedAttributed}
|
||||
private _attributes: {[name: string]: IndexedAttributed};
|
||||
private _indicesInsertIndex: number = 0;
|
||||
private _maxIndex: number = 0;
|
||||
private _compiled: boolean = false;
|
||||
@ -71,7 +70,7 @@ export class SegmentedBuffer {
|
||||
this._attributes[attr.name] = {
|
||||
name: attr.name,
|
||||
numComponents: attr.numComponents,
|
||||
insertIndex: 0
|
||||
insertIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -83,12 +82,12 @@ export class SegmentedBuffer {
|
||||
indices: {
|
||||
numComponents: 1,
|
||||
data: new Uint16Array(this._bufferSize),
|
||||
}
|
||||
},
|
||||
};
|
||||
for (const attr in this._attributes) {
|
||||
this._buffer[attr] = {
|
||||
numComponents: this._attributes[attr].numComponents,
|
||||
data: new Float32Array(this._bufferSize)
|
||||
data: new Float32Array(this._bufferSize),
|
||||
};
|
||||
this._attributes[attr].insertIndex = 0;
|
||||
}
|
||||
@ -107,7 +106,7 @@ export class SegmentedBuffer {
|
||||
|
||||
private _willOverflow(data: VoxelData): boolean {
|
||||
// Check for indices Uint16 overflow
|
||||
//const dataMaxIndex = Math.max(...data.indices);
|
||||
// const dataMaxIndex = Math.max(...data.indices);
|
||||
const dataMaxIndex = data.indices.reduce((a, v) => Math.max(a, v));
|
||||
if ((this._maxIndex + dataMaxIndex) > 65535) {
|
||||
return true;
|
||||
@ -132,7 +131,7 @@ export class SegmentedBuffer {
|
||||
throw `Given data does not have indices data`;
|
||||
}
|
||||
*/
|
||||
//const setsRequired = Math.max(...data.indices) + 1;
|
||||
// const setsRequired = Math.max(...data.indices) + 1;
|
||||
const setsRequired = data.indices.reduce((a, v) => Math.max(a, v)) + 1;
|
||||
for (const attr in this._attributes) {
|
||||
if (!(attr in data)) {
|
||||
@ -143,31 +142,31 @@ export class SegmentedBuffer {
|
||||
}
|
||||
const numSets = data[attr].length / this._attributes[attr].numComponents;
|
||||
if (numSets != setsRequired) {
|
||||
//throw `Number of indices does not match number of ${attr} components given`;
|
||||
// throw `Number of indices does not match number of ${attr} components given`;
|
||||
throw Error(`Expected ${setsRequired * this._attributes[attr].numComponents} values for ${attr}, got ${data[attr].length}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _addDataToAttribute(attr: string, attr_data: (Uint16Array | Float32Array | Array<number>)) {
|
||||
private _addDataToAttribute(attr: string, attrData: (Uint16Array | Float32Array | Array<number>)) {
|
||||
const indexOffset = this._attributes[attr].insertIndex;
|
||||
attr_data.forEach((value, i) => {
|
||||
attrData.forEach((value, i) => {
|
||||
this._buffer[attr].data[i + indexOffset] = value;
|
||||
});
|
||||
this._attributes[attr].insertIndex += attr_data.length;
|
||||
this._attributes[attr].insertIndex += attrData.length;
|
||||
}
|
||||
|
||||
public add(data: VoxelData) {
|
||||
if (this._compiled) {
|
||||
throw Error("Buffer already compiled, cannot add more data");
|
||||
throw Error('Buffer already compiled, cannot add more data');
|
||||
}
|
||||
|
||||
|
||||
if (this._sanityCheck) {
|
||||
this._checkDataMatchesAttributes(data);
|
||||
}
|
||||
|
||||
if (this._willOverflow(data)) {
|
||||
//console.log("Cycling buffer...");
|
||||
// console.log("Cycling buffer...");
|
||||
this._cycle();
|
||||
}
|
||||
|
||||
@ -178,11 +177,10 @@ export class SegmentedBuffer {
|
||||
|
||||
this._indicesInsertIndex += data.indices.length;
|
||||
this._maxIndex += 1 + data.indices.reduce((a, v) => Math.max(a, v));
|
||||
|
||||
|
||||
for (const attr in this._attributes) {
|
||||
this._addDataToAttribute(attr, data[attr]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -198,27 +196,24 @@ export class SegmentedBuffer {
|
||||
this._completeBuffers.forEach((buffer, i) => {
|
||||
this.WebGLBuffers[i] = {
|
||||
buffer: twgl.createBufferInfoFromArrays(gl, buffer.buffer),
|
||||
numElements: buffer.numElements
|
||||
numElements: buffer.numElements,
|
||||
};
|
||||
});
|
||||
|
||||
this._compiled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class BottomlessBuffer {
|
||||
|
||||
public WebGLBuffers: Array<{
|
||||
buffer: twgl.BufferInfo,
|
||||
numElements: number
|
||||
}>
|
||||
}>;
|
||||
|
||||
private _completeBuffers: Array<CompleteBuffer>;
|
||||
private _buffer!: BottomlessBufferData;
|
||||
private _attributes: {[name: string]: Attribute}
|
||||
private _attributes: {[name: string]: Attribute};
|
||||
private _maxIndex: number = 0;
|
||||
private _compiled: boolean = false;
|
||||
private _sanityCheck: boolean = true;
|
||||
@ -233,7 +228,7 @@ export class BottomlessBuffer {
|
||||
for (const attr of attributes) {
|
||||
this._attributes[attr.name] = {
|
||||
name: attr.name,
|
||||
numComponents: attr.numComponents
|
||||
numComponents: attr.numComponents,
|
||||
};
|
||||
}
|
||||
|
||||
@ -245,12 +240,12 @@ export class BottomlessBuffer {
|
||||
|
||||
_getNewBuffer() {
|
||||
this._buffer = {
|
||||
indices: {numComponents: 1, data: []}
|
||||
indices: {numComponents: 1, data: []},
|
||||
};
|
||||
for (const attr in this._attributes) {
|
||||
this._buffer[attr] = {
|
||||
numComponents: this._attributes[attr].numComponents,
|
||||
data: []
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -267,14 +262,14 @@ export class BottomlessBuffer {
|
||||
|
||||
_willOverflow(data: VoxelData) {
|
||||
// Check for indices Uint16 overflow
|
||||
//const dataMaxIndex = Math.max(...data.indices);
|
||||
// const dataMaxIndex = Math.max(...data.indices);
|
||||
const dataMaxIndex = data.indices.reduce((a, v) => Math.max(a, v));
|
||||
return ((this._maxIndex + dataMaxIndex) > 65535);
|
||||
}
|
||||
|
||||
_checkDataMatchesAttributes(data: VoxelData) {
|
||||
if (!('indices'in data)) {
|
||||
throw `Given data does not have indices data`;
|
||||
if (!('indices' in data)) {
|
||||
throw Error('Given data does not have indices data');
|
||||
}
|
||||
const setsRequired = data.indices.reduce((a, v) => Math.max(a, v)) + 1;
|
||||
for (const attr in this._attributes) {
|
||||
@ -286,7 +281,7 @@ export class BottomlessBuffer {
|
||||
}
|
||||
const numSets = data[attr].length / this._attributes[attr].numComponents;
|
||||
if (numSets != setsRequired) {
|
||||
//throw `Number of indices does not match number of ${attr} components given`;
|
||||
// throw `Number of indices does not match number of ${attr} components given`;
|
||||
throw Error(`Expected ${setsRequired * this._attributes[attr].numComponents} values for ${attr}, got ${data[attr].length}`);
|
||||
}
|
||||
}
|
||||
@ -295,26 +290,26 @@ export class BottomlessBuffer {
|
||||
|
||||
add(data: VoxelData) {
|
||||
if (this._compiled) {
|
||||
throw Error("Buffer already compiled, cannot add more data");
|
||||
throw Error('Buffer already compiled, cannot add more data');
|
||||
}
|
||||
|
||||
|
||||
if (this._sanityCheck) {
|
||||
this._checkDataMatchesAttributes(data);
|
||||
}
|
||||
|
||||
if (this._willOverflow(data)) {
|
||||
//console.log("Cycling buffer...");
|
||||
// console.log("Cycling buffer...");
|
||||
this._cycle();
|
||||
}
|
||||
|
||||
// Add the new indices data
|
||||
data.indices.forEach(index => {
|
||||
data.indices.forEach((index) => {
|
||||
this._buffer.indices.data.push(index + this._maxIndex);
|
||||
});
|
||||
this._maxIndex += 1 + data.indices.reduce((a, v) => Math.max(a, v));
|
||||
|
||||
|
||||
for (const attr in this._attributes) {
|
||||
data[attr].forEach(v => {
|
||||
data[attr].forEach((v) => {
|
||||
this._buffer[attr].data.push(v);
|
||||
});
|
||||
}
|
||||
@ -332,11 +327,10 @@ export class BottomlessBuffer {
|
||||
this._completeBuffers.forEach((buffer, i) => {
|
||||
this.WebGLBuffers[i] = {
|
||||
buffer: twgl.createBufferInfoFromArrays(gl, buffer.buffer),
|
||||
numElements: buffer.numElements
|
||||
numElements: buffer.numElements,
|
||||
};
|
||||
});
|
||||
|
||||
this._compiled = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
import { m4, v3 } from "twgl.js";
|
||||
import { MouseManager } from "./mouse";
|
||||
import { degreesToRadians, clamp } from "./math";
|
||||
import { Renderer } from "./renderer";
|
||||
import { m4, v3 } from 'twgl.js';
|
||||
import { MouseManager } from './mouse';
|
||||
import { degreesToRadians, clamp } from './math';
|
||||
import { Renderer } from './renderer';
|
||||
|
||||
export class ArcballCamera {
|
||||
|
||||
public isUserRotating = false;
|
||||
|
||||
private readonly fov: number;
|
||||
private readonly zNear: number;
|
||||
private readonly zFar: number;
|
||||
private readonly cameraSmoothing = 0.025;
|
||||
|
||||
|
||||
public aspect: number;
|
||||
private actualDistance = 18.0;
|
||||
private actualAzimuth = -1.0;
|
||||
@ -53,7 +52,7 @@ export class ArcballCamera {
|
||||
|
||||
public updateCamera() {
|
||||
this.aspect = this.gl.canvas.width / this.gl.canvas.height;
|
||||
|
||||
|
||||
// Update target location if user is rotating camera
|
||||
if (this.isUserRotating) {
|
||||
const mouseDelta = MouseManager.Get.getMouseDelta();
|
||||
@ -73,7 +72,7 @@ export class ArcballCamera {
|
||||
this.eye = [
|
||||
this.actualDistance * Math.cos(this.actualAzimuth) * -Math.sin(this.actualElevation),
|
||||
this.actualDistance * Math.cos(this.actualElevation),
|
||||
this.actualDistance * Math.sin(this.actualAzimuth) * -Math.sin(this.actualElevation)
|
||||
this.actualDistance * Math.sin(this.actualAzimuth) * -Math.sin(this.actualElevation),
|
||||
];
|
||||
}
|
||||
|
||||
@ -83,7 +82,7 @@ export class ArcballCamera {
|
||||
return [
|
||||
this.actualDistance * Math.cos(azimuth ) * -Math.sin(elevation),
|
||||
this.actualDistance * Math.cos(elevation),
|
||||
this.actualDistance * Math.sin(azimuth) * -Math.sin(elevation)
|
||||
this.actualDistance * Math.sin(azimuth) * -Math.sin(elevation),
|
||||
];
|
||||
}
|
||||
|
||||
@ -138,19 +137,18 @@ export class ArcballCamera {
|
||||
const inverseProjectionMatrix = this.getInverseWorldViewProjection();
|
||||
var origin = mathUtil.multiplyMatVec4(inverseProjectionMatrix, [mousePos.x, mousePos.y, -1.0, 1.0]);
|
||||
var dest = mathUtil.multiplyMatVec4(inverseProjectionMatrix, [mousePos.x, mousePos.y, 1.0, 1.0]);
|
||||
|
||||
|
||||
origin[0] /= origin[3];
|
||||
origin[1] /= origin[3];
|
||||
origin[2] /= origin[3];
|
||||
dest[0] /= dest[3];
|
||||
dest[1] /= dest[3];
|
||||
dest[2] /= dest[3];
|
||||
|
||||
|
||||
return {origin: origin, dest: dest};
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports.ArcballCamera = ArcballCamera;
|
||||
module.exports.ArcballCamera = ArcballCamera;
|
||||
|
@ -1,28 +1,26 @@
|
||||
import { Schematic, Litematic } from "./schematic";
|
||||
import { AppContext } from "./app_context";
|
||||
import { ArcballCamera } from "./camera";
|
||||
import { MouseManager } from "./mouse";
|
||||
import { AppContext } from './app_context';
|
||||
import { ArcballCamera } from './camera';
|
||||
import { MouseManager } from './mouse';
|
||||
|
||||
function AddEvent(htmlElementID: string, event: string, delegate: (e: any) => void) {
|
||||
function addEvent(htmlElementID: string, event: string, delegate: (e: any) => void) {
|
||||
document.getElementById(htmlElementID)?.addEventListener(event, delegate);
|
||||
}
|
||||
|
||||
// Register Events
|
||||
/*
|
||||
AddEvent("buttonChooseFile", "click", () => { context.do(Action.Load); });
|
||||
AddEvent("inputFile", "click", () => { context.do(Action.Load); });
|
||||
AddEvent("buttonVoxelise", "click", () => { context.do(Action.Voxelise); });
|
||||
AddEvent("buttonSchematic", "click", async () => { context.do(Action.ExportSchematic); });
|
||||
AddEvent("buttonLitematic", "click", async () => { context.do(Action.ExportLitematic); });
|
||||
*/
|
||||
|
||||
const camera = ArcballCamera.Get;
|
||||
AddEvent("canvas", "mousedown", (e) => { camera.onMouseDown(e); });
|
||||
AddEvent("canvas", "mouseup", (e) => { camera.onMouseUp(e); });
|
||||
AddEvent("canvas", "wheel", (e) => { camera.onWheelScroll(e); });
|
||||
addEvent('canvas', 'mousedown', (e) => {
|
||||
camera.onMouseDown(e);
|
||||
});
|
||||
addEvent('canvas', 'mouseup', (e) => {
|
||||
camera.onMouseUp(e);
|
||||
});
|
||||
addEvent('canvas', 'wheel', (e) => {
|
||||
camera.onWheelScroll(e);
|
||||
});
|
||||
|
||||
const mouseManager = MouseManager.Get;
|
||||
AddEvent("canvas", "mousemove", (e) => { mouseManager.onMouseMove(e); });
|
||||
addEvent('canvas', 'mousemove', (e) => {
|
||||
mouseManager.onMouseMove(e);
|
||||
});
|
||||
|
||||
|
||||
// Begin draw loop
|
||||
@ -31,4 +29,4 @@ function render() {
|
||||
context.draw();
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
requestAnimationFrame(render);
|
||||
requestAnimationFrame(render);
|
||||
|
@ -10,5 +10,5 @@ export namespace AppConfig {
|
||||
|
||||
/** Recomended, better matches colours */
|
||||
export const DITHERING_ENABLED = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
import * as twgl from "twgl.js";
|
||||
import { Triangle } from "./triangle";
|
||||
import { Vector3 } from "./vector";
|
||||
import { VoxelData } from "./buffer";
|
||||
import * as twgl from 'twgl.js';
|
||||
import { Triangle } from './triangle';
|
||||
import { Vector3 } from './vector';
|
||||
import { VoxelData } from './buffer';
|
||||
|
||||
|
||||
export class GeometryTemplates {
|
||||
|
||||
private static readonly _default_cube = twgl.primitives.createCubeVertices(1.0);
|
||||
|
||||
|
||||
static getTriangleBufferData(triangle: Triangle, debug: boolean): VoxelData {
|
||||
const a = triangle.v0;
|
||||
const b = triangle.v1;
|
||||
const c = triangle.v2;
|
||||
const n = triangle.normal;
|
||||
//console.log(triangle);
|
||||
// console.log(triangle);
|
||||
|
||||
if (debug) {
|
||||
return {
|
||||
@ -25,8 +24,8 @@ export class GeometryTemplates {
|
||||
indices: new Uint16Array([
|
||||
0, 1,
|
||||
1, 2,
|
||||
2, 0
|
||||
])
|
||||
2, 0,
|
||||
]),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@ -38,16 +37,16 @@ export class GeometryTemplates {
|
||||
texcoord: new Float32Array([
|
||||
a.texcoord.u, a.texcoord.v,
|
||||
b.texcoord.u, b.texcoord.v,
|
||||
c.texcoord.u, c.texcoord.v
|
||||
c.texcoord.u, c.texcoord.v,
|
||||
]),
|
||||
normal: new Float32Array([
|
||||
n.x, n.y, n.z,
|
||||
n.x, n.y, n.z,
|
||||
n.x, n.y, n.z
|
||||
n.x, n.y, n.z,
|
||||
]),
|
||||
indices: new Uint16Array([
|
||||
0, 1, 2
|
||||
])
|
||||
0, 1, 2,
|
||||
]),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -55,7 +54,7 @@ export class GeometryTemplates {
|
||||
static getBoxBufferData(centre: Vector3, debug: boolean): VoxelData {
|
||||
const a = Vector3.subScalar(centre, 0.5);
|
||||
const b = Vector3.addScalar(centre, 0.5);
|
||||
|
||||
|
||||
if (debug) {
|
||||
return {
|
||||
position: new Float32Array([
|
||||
@ -66,27 +65,27 @@ export class GeometryTemplates {
|
||||
a.x, a.y, b.z,
|
||||
b.x, a.y, b.z,
|
||||
b.x, b.y, b.z,
|
||||
a.x, b.y, b.z
|
||||
a.x, b.y, b.z,
|
||||
]),
|
||||
indices: new Uint16Array([
|
||||
0, 1, 1, 2, 2, 3, 3, 0,
|
||||
4, 5, 5, 6, 6, 7, 7, 4,
|
||||
0, 4, 1, 5, 2, 6, 3, 7
|
||||
])
|
||||
0, 4, 1, 5, 2, 6, 3, 7,
|
||||
]),
|
||||
};
|
||||
} else {
|
||||
let cube = {
|
||||
const cube = {
|
||||
position: new Float32Array(72),
|
||||
texcoord: new Float32Array(48),
|
||||
normal: new Float32Array(72),
|
||||
indices: new Uint16Array(72),
|
||||
};
|
||||
|
||||
|
||||
cube.position.set(GeometryTemplates._default_cube.position);
|
||||
cube.normal.set(GeometryTemplates._default_cube.normal);
|
||||
cube.indices.set(GeometryTemplates._default_cube.indices);
|
||||
cube.texcoord.set(GeometryTemplates._default_cube.texcoord);
|
||||
|
||||
|
||||
for (let i = 0; i < 72; i += 3) {
|
||||
cube.position[i + 0] += centre.x;
|
||||
cube.position[i + 1] += centre.y;
|
||||
@ -96,5 +95,4 @@ export class GeometryTemplates {
|
||||
return cube;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ export abstract class Hashable {
|
||||
|
||||
|
||||
export class HashSet<T extends Hashable> {
|
||||
|
||||
private _numBins: number;
|
||||
protected _bins: Array<Array<T>>;
|
||||
|
||||
@ -39,12 +38,10 @@ export class HashSet<T extends Hashable> {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class HashMap<K extends Hashable, V> {
|
||||
|
||||
private _numBins: number;
|
||||
protected _bins: Array<Array<{key: K, value: V}>>;
|
||||
|
||||
@ -73,11 +70,12 @@ export class HashMap<K extends Hashable, V> {
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
public has(item: K): boolean {
|
||||
const binIndex = this._getBin(item);
|
||||
|
||||
const list = this._bins[binIndex];
|
||||
for (const {key, value} of list) {
|
||||
for (const { key, value } of list) {
|
||||
if (item.equals(key)) {
|
||||
return true;
|
||||
}
|
||||
@ -85,10 +83,10 @@ export class HashMap<K extends Hashable, V> {
|
||||
|
||||
return false;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
public add(key: K, value: V) {
|
||||
const binIndex = this._getBin(key);
|
||||
this._bins[binIndex].push({key, value});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
26
src/main.ts
26
src/main.ts
@ -1,6 +1,6 @@
|
||||
import { app, BrowserWindow, Tray } from "electron";
|
||||
import path from "path";
|
||||
import url from "url";
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
@ -8,11 +8,11 @@ let mainWindow: BrowserWindow;
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
//const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize;
|
||||
// const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize;
|
||||
const width = 1400;
|
||||
const height = 800;
|
||||
|
||||
//const appIcon = new Tray("../resources/icon.png");
|
||||
// const appIcon = new Tray("../resources/icon.png");
|
||||
mainWindow = new BrowserWindow({
|
||||
width: width,
|
||||
height: height,
|
||||
@ -21,8 +21,8 @@ function createWindow() {
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true
|
||||
}
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
});
|
||||
mainWindow.removeMenu();
|
||||
|
||||
@ -31,14 +31,14 @@ function createWindow() {
|
||||
mainWindow.loadURL(url.format({
|
||||
pathname: path.join(__dirname, '../index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
slashes: true,
|
||||
}));
|
||||
|
||||
// Open the DevTools.
|
||||
//mainWindow.webContents.openDevTools();
|
||||
// mainWindow.webContents.openDevTools();
|
||||
|
||||
// Emitted when the window is closed.
|
||||
mainWindow.on('closed', function () {
|
||||
mainWindow.on('closed', function() {
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
@ -49,7 +49,7 @@ function createWindow() {
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', function () {
|
||||
app.on('window-all-closed', function() {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
@ -57,10 +57,10 @@ app.on('window-all-closed', function () {
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', function () {
|
||||
app.on('activate', function() {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
22
src/math.ts
22
src/math.ts
@ -1,45 +1,45 @@
|
||||
import { Vector3 } from "./vector";
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
|
||||
export const argMax = (array: [number]) => {
|
||||
return array.map((x, i) => [x, i]).reduce((r, a) => (a[0] > r[0] ? a : r))[1];
|
||||
}
|
||||
};
|
||||
|
||||
export const fastCrossXAxis = (vec: Vector3) => {
|
||||
return new Vector3(0.0, -vec.z, vec.y);
|
||||
}
|
||||
};
|
||||
|
||||
export const fastCrossYAxis = (vec: Vector3) => {
|
||||
return new Vector3(vec.z, 0.0, -vec.x);
|
||||
}
|
||||
};
|
||||
|
||||
export const fastCrossZAxis = (vec: Vector3) => {
|
||||
return new Vector3(-vec.y, vec.x, 0.0);
|
||||
}
|
||||
};
|
||||
|
||||
export const clamp = (value: number, min: number, max: number) => {
|
||||
return Math.max(Math.min(max, value), min);
|
||||
}
|
||||
};
|
||||
|
||||
export const floorToNearest = (value: number, base: number) => {
|
||||
return Math.floor(value / base) * base;
|
||||
}
|
||||
};
|
||||
|
||||
export const ceilToNearest = (value: number, base: number) => {
|
||||
return Math.ceil(value / base) * base;
|
||||
}
|
||||
};
|
||||
|
||||
export const roundToNearest = (value: number, base: number) => {
|
||||
return Math.round(value / base) * base;
|
||||
}
|
||||
};
|
||||
|
||||
export const triangleArea = (a: number, b: number, c: number) => {
|
||||
const p = (a + b + c) / 2;
|
||||
return Math.sqrt(p * (p - a) * (p - b) * (p - c));
|
||||
}
|
||||
};
|
||||
|
||||
export const xAxis = new Vector3(1.0, 0.0, 0.0);
|
||||
export const yAxis = new Vector3(0.0, 1.0, 0.0);
|
||||
export const zAxis = new Vector3(0.0, 0.0, 1.0);
|
||||
|
||||
export const degreesToRadians = Math.PI / 180;
|
||||
export const degreesToRadians = Math.PI / 180;
|
||||
|
292
src/mesh.ts
292
src/mesh.ts
@ -1,20 +1,18 @@
|
||||
import * as twgl from "twgl.js";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as twgl from 'twgl.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { Triangle } from "./triangle";
|
||||
import { Vector3 } from "./vector";
|
||||
import { RGB, UV } from "./util";
|
||||
import { TextureFormat } from "./texture";
|
||||
import { triangleArea } from "./math";
|
||||
import { Triangle } from './triangle';
|
||||
import { Vector3 } from './vector';
|
||||
import { RGB, UV } from './util';
|
||||
import { TextureFormat } from './texture';
|
||||
import { triangleArea } from './math';
|
||||
|
||||
type VertexMap<T> = { [index: number]: T };
|
||||
|
||||
|
||||
export class Vertex {
|
||||
|
||||
public position: Vector3;
|
||||
public texcoord: UV
|
||||
public texcoord: UV;
|
||||
public normal: Vector3;
|
||||
|
||||
constructor(position: Vector3, texcoord: UV, normal: Vector3) {
|
||||
@ -38,7 +36,7 @@ export class Vertex {
|
||||
vertexTexcoordMap: VertexMap<UV>,
|
||||
vertexNormalMap: VertexMap<Vector3>,
|
||||
) {
|
||||
const tokens = vertexToken.split("/");
|
||||
const tokens = vertexToken.split('/');
|
||||
|
||||
const positionIndex = parseFloat(tokens[0]);
|
||||
const texcoordIndex = parseFloat(tokens[1]);
|
||||
@ -50,7 +48,6 @@ export class Vertex {
|
||||
|
||||
return new Vertex(position, texcoord, normal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -75,10 +72,12 @@ export interface TextureMaterial {
|
||||
format: TextureFormat
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
export enum MaterialType {
|
||||
Texture,
|
||||
Fill
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
export interface MaterialTriangles {
|
||||
material: (FillMaterial | TextureMaterial);
|
||||
@ -86,7 +85,6 @@ export interface MaterialTriangles {
|
||||
}
|
||||
|
||||
class Material {
|
||||
|
||||
public name: string;
|
||||
public faces: Array<Triangle>;
|
||||
public materialData: (FillMaterial | TextureMaterial);
|
||||
@ -95,7 +93,7 @@ class Material {
|
||||
this.name = name;
|
||||
this.faces = [];
|
||||
|
||||
let material: FillMaterial = { diffuseColour: { r: 1, g: 1, b: 1 }, type: MaterialType.Fill}
|
||||
const material: FillMaterial = { diffuseColour: { r: 1, g: 1, b: 1 }, type: MaterialType.Fill};
|
||||
this.materialData = material;
|
||||
}
|
||||
|
||||
@ -104,35 +102,33 @@ class Material {
|
||||
}
|
||||
|
||||
public isFilled(): boolean {
|
||||
return this.name !== "" && this.faces.length > 0;
|
||||
return this.name !== '' && this.faces.length > 0;
|
||||
}
|
||||
|
||||
public attachMTLData(materialData: (FillMaterial | TextureMaterial)) {
|
||||
this.materialData = materialData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Mesh {
|
||||
|
||||
public materials: Array<Material>
|
||||
public materials: Array<Material>;
|
||||
|
||||
constructor(objPathString: string, gl: WebGLRenderingContext) {
|
||||
// Parse .obj
|
||||
const wavefrontString = fs.readFileSync(objPathString).toString('utf8');
|
||||
const parsedOBJ = this._parseOBJFile(wavefrontString);
|
||||
|
||||
|
||||
// TODO: Create blank .mtl when not found
|
||||
if (!parsedOBJ.mtlPath) {
|
||||
throw Error("No .mtl file found.");
|
||||
throw Error('No .mtl file found.');
|
||||
}
|
||||
|
||||
|
||||
const objPath = path.parse(objPathString);
|
||||
if (!path.isAbsolute(parsedOBJ.mtlPath)) {
|
||||
parsedOBJ.mtlPath = path.join(objPath.dir, parsedOBJ.mtlPath);
|
||||
}
|
||||
parsedOBJ.mtlPath = parsedOBJ.mtlPath.trimEnd();
|
||||
|
||||
|
||||
// Parse .mtl
|
||||
const materialString = fs.readFileSync(parsedOBJ.mtlPath).toString('utf8');
|
||||
const parsedMTL = this._parseMaterial(materialString, objPath);
|
||||
@ -146,29 +142,29 @@ export class Mesh {
|
||||
}
|
||||
|
||||
private _addMaterial(materialsJSON: Materials, materialName: string, materialDiffuseColour: RGB, materialDiffuseTexturePath: string, materialFormat: TextureFormat) {
|
||||
if (materialDiffuseTexturePath !== "") {
|
||||
if (materialDiffuseTexturePath !== '') {
|
||||
materialsJSON[materialName] = {
|
||||
texturePath: materialDiffuseTexturePath,
|
||||
type: MaterialType.Texture,
|
||||
format: materialFormat
|
||||
format: materialFormat,
|
||||
};
|
||||
} else if (materialName !== "") {
|
||||
} else if (materialName !== '') {
|
||||
materialsJSON[materialName] = {
|
||||
diffuseColour: materialDiffuseColour,
|
||||
type: MaterialType.Fill
|
||||
type: MaterialType.Fill,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rewrite
|
||||
private _parseMaterial(materialString: string, objPath: path.ParsedPath): Materials {
|
||||
var materialsJSON: Materials = {};
|
||||
const materialsJSON: Materials = {};
|
||||
|
||||
const lines = materialString.split('\n');
|
||||
|
||||
let materialName: string = "";
|
||||
let materialName: string = '';
|
||||
let materialDiffuseColour: RGB = { r: 1.0, g: 1.0, b: 1.0 };
|
||||
let materialDiffuseTexturePath: string = "";
|
||||
let materialDiffuseTexturePath: string = '';
|
||||
let materialTextureFormat: TextureFormat = TextureFormat.PNG;
|
||||
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
@ -176,50 +172,49 @@ export class Mesh {
|
||||
const lineTokens = line.trim().split(/\s+/);
|
||||
|
||||
switch (lineTokens[0]) {
|
||||
case "newmtl":
|
||||
this._addMaterial(materialsJSON, materialName, materialDiffuseColour, materialDiffuseTexturePath, materialTextureFormat);
|
||||
materialName = lineTokens[1];
|
||||
materialDiffuseColour = { r: 0, g: 0, b: 0 };
|
||||
materialDiffuseTexturePath = ""
|
||||
break;
|
||||
case 'newmtl':
|
||||
this._addMaterial(materialsJSON, materialName, materialDiffuseColour, materialDiffuseTexturePath, materialTextureFormat);
|
||||
materialName = lineTokens[1];
|
||||
materialDiffuseColour = { r: 0, g: 0, b: 0 };
|
||||
materialDiffuseTexturePath = '';
|
||||
break;
|
||||
|
||||
case "Kd":
|
||||
const diffuseColour = lineTokens.slice(1).map(x => parseFloat(x))
|
||||
if (!diffuseColour || diffuseColour.length != 3) {
|
||||
throw Error(`Could not parse .mtl file. (Line ${i + 1})`);
|
||||
}
|
||||
if (diffuseColour.some(x => Number.isNaN(x))) {
|
||||
throw Error(`Could not parse .mtl file. (Line ${i + 1})`);
|
||||
}
|
||||
materialDiffuseColour = {
|
||||
r: diffuseColour[0], g: diffuseColour[1], b: diffuseColour[2]
|
||||
};
|
||||
break;
|
||||
case 'Kd':
|
||||
const diffuseColour = lineTokens.slice(1).map((x) => parseFloat(x));
|
||||
if (!diffuseColour || diffuseColour.length != 3) {
|
||||
throw Error(`Could not parse .mtl file. (Line ${i + 1})`);
|
||||
}
|
||||
if (diffuseColour.some((x) => Number.isNaN(x))) {
|
||||
throw Error(`Could not parse .mtl file. (Line ${i + 1})`);
|
||||
}
|
||||
materialDiffuseColour = {
|
||||
r: diffuseColour[0], g: diffuseColour[1], b: diffuseColour[2],
|
||||
};
|
||||
break;
|
||||
|
||||
case "map_Kd":
|
||||
if (!lineTokens[1]) {
|
||||
throw Error(`No valid path to texture in .mtl file. (Line ${i + 1})`);
|
||||
}
|
||||
let texturePath = lineTokens[1];
|
||||
if (!path.isAbsolute(texturePath)) {
|
||||
texturePath = path.join(objPath.dir, texturePath);
|
||||
}
|
||||
if (!fs.existsSync(texturePath)) {
|
||||
console.error(texturePath);
|
||||
throw Error(`Cannot load texture ${texturePath}`);
|
||||
}
|
||||
const _path = path.parse(texturePath);
|
||||
if (".png" === _path.ext.toLowerCase()) {
|
||||
materialTextureFormat = TextureFormat.PNG;
|
||||
}
|
||||
else if ([".jpeg", ".jpg"].includes(_path.ext.toLowerCase())) {
|
||||
materialTextureFormat = TextureFormat.JPEG;
|
||||
} else {
|
||||
throw Error(`Can only load PNG and JPEG textures`);
|
||||
}
|
||||
case 'map_Kd':
|
||||
if (!lineTokens[1]) {
|
||||
throw Error(`No valid path to texture in .mtl file. (Line ${i + 1})`);
|
||||
}
|
||||
let texturePath = lineTokens[1];
|
||||
if (!path.isAbsolute(texturePath)) {
|
||||
texturePath = path.join(objPath.dir, texturePath);
|
||||
}
|
||||
if (!fs.existsSync(texturePath)) {
|
||||
console.error(texturePath);
|
||||
throw Error(`Cannot load texture ${texturePath}`);
|
||||
}
|
||||
const _path = path.parse(texturePath);
|
||||
if ('.png' === _path.ext.toLowerCase()) {
|
||||
materialTextureFormat = TextureFormat.PNG;
|
||||
} else if (['.jpeg', '.jpg'].includes(_path.ext.toLowerCase())) {
|
||||
materialTextureFormat = TextureFormat.JPEG;
|
||||
} else {
|
||||
throw Error(`Can only load PNG and JPEG textures`);
|
||||
}
|
||||
|
||||
materialDiffuseTexturePath = texturePath;
|
||||
break;
|
||||
materialDiffuseTexturePath = texturePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,7 +225,7 @@ export class Mesh {
|
||||
|
||||
|
||||
private _mergeMaterialData(parsedOBJ: ParsedOBJ, parsedMTL: Materials): Array<Material> {
|
||||
parsedOBJ.materials.forEach(material => {
|
||||
parsedOBJ.materials.forEach((material) => {
|
||||
material.attachMTLData(parsedMTL[material.name]);
|
||||
});
|
||||
|
||||
@ -239,82 +234,82 @@ export class Mesh {
|
||||
|
||||
|
||||
private _parseOBJFile(wavefrontString: string): ParsedOBJ {
|
||||
const lines = wavefrontString.split("\n");
|
||||
const lines = wavefrontString.split('\n');
|
||||
|
||||
let mtlPath: (string | undefined);
|
||||
|
||||
let vertexPositionMap: VertexMap<Vector3> = {};
|
||||
let vertexTexcoordMap: VertexMap<UV> = {};
|
||||
let vertexNormalMap: VertexMap<Vector3> = {};
|
||||
const vertexPositionMap: VertexMap<Vector3> = {};
|
||||
const vertexTexcoordMap: VertexMap<UV> = {};
|
||||
const vertexNormalMap: VertexMap<Vector3> = {};
|
||||
|
||||
let vertexPositionIndex = 1;
|
||||
let vertexTexcoordIndex = 1;
|
||||
let vertexNormalIndex = 1;
|
||||
|
||||
let currentMaterial: Material = new Material("");
|
||||
let currentMaterial: Material = new Material('');
|
||||
|
||||
let materialMap: {[name: string]: Material} = {};
|
||||
//let materials: Array<Material> = [];
|
||||
const materialMap: {[name: string]: Material} = {};
|
||||
// let materials: Array<Material> = [];
|
||||
|
||||
lines.forEach(line => {
|
||||
const tokens = line.split(" ");
|
||||
lines.forEach((line) => {
|
||||
const tokens = line.split(' ');
|
||||
switch (tokens[0]) {
|
||||
case "mtllib":
|
||||
mtlPath = tokens[1];
|
||||
break;
|
||||
case "v":
|
||||
vertexPositionMap[vertexPositionIndex++] = Vector3.parse(line);
|
||||
break;
|
||||
case "vn":
|
||||
vertexNormalMap[vertexNormalIndex++] = Vector3.parse(line);
|
||||
break;
|
||||
case "vt":
|
||||
vertexTexcoordMap[vertexTexcoordIndex++] = { u: parseFloat(tokens[1]), v: parseFloat(tokens[2]) };
|
||||
break;
|
||||
case 'mtllib':
|
||||
mtlPath = tokens[1];
|
||||
break;
|
||||
case 'v':
|
||||
vertexPositionMap[vertexPositionIndex++] = Vector3.parse(line);
|
||||
break;
|
||||
case 'vn':
|
||||
vertexNormalMap[vertexNormalIndex++] = Vector3.parse(line);
|
||||
break;
|
||||
case 'vt':
|
||||
vertexTexcoordMap[vertexTexcoordIndex++] = { u: parseFloat(tokens[1]), v: parseFloat(tokens[2]) };
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
lines.forEach(line => {
|
||||
lines.forEach((line) => {
|
||||
line = line.replace(/[\n\r]/g, '').trimEnd();
|
||||
const tokens = line.split(" ");
|
||||
const tokens = line.split(' ');
|
||||
switch (tokens[0]) {
|
||||
case "usemtl":
|
||||
if (currentMaterial.isFilled() && !(currentMaterial.name in materialMap)) {
|
||||
materialMap[currentMaterial.name] = currentMaterial;
|
||||
}
|
||||
if (tokens[1] in materialMap) {
|
||||
console.log("Material already found", tokens[1]);
|
||||
currentMaterial = materialMap[tokens[1]];
|
||||
} else {
|
||||
console.log("New material", tokens[1])
|
||||
currentMaterial = new Material(tokens[1]);
|
||||
}
|
||||
break;
|
||||
case "f":
|
||||
if (tokens.length === 5) {
|
||||
// QUAD
|
||||
const v1 = Vertex.parseFromOBJ(tokens[1], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v2 = Vertex.parseFromOBJ(tokens[2], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v3 = Vertex.parseFromOBJ(tokens[3], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v4 = Vertex.parseFromOBJ(tokens[4], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
case 'usemtl':
|
||||
if (currentMaterial.isFilled() && !(currentMaterial.name in materialMap)) {
|
||||
materialMap[currentMaterial.name] = currentMaterial;
|
||||
}
|
||||
if (tokens[1] in materialMap) {
|
||||
console.log('Material already found', tokens[1]);
|
||||
currentMaterial = materialMap[tokens[1]];
|
||||
} else {
|
||||
console.log('New material', tokens[1]);
|
||||
currentMaterial = new Material(tokens[1]);
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (tokens.length === 5) {
|
||||
// QUAD
|
||||
const v1 = Vertex.parseFromOBJ(tokens[1], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v2 = Vertex.parseFromOBJ(tokens[2], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v3 = Vertex.parseFromOBJ(tokens[3], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v4 = Vertex.parseFromOBJ(tokens[4], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
|
||||
const face = new Triangle(v4.copy(), v2.copy(), v1.copy());
|
||||
const face2 = new Triangle(v4.copy(), v3.copy(), v2.copy());
|
||||
const face = new Triangle(v4.copy(), v2.copy(), v1.copy());
|
||||
const face2 = new Triangle(v4.copy(), v3.copy(), v2.copy());
|
||||
|
||||
currentMaterial.addFace(face);
|
||||
currentMaterial.addFace(face2);
|
||||
} else if (tokens.length === 4) {
|
||||
// TRI
|
||||
const v0 = Vertex.parseFromOBJ(tokens[1], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v1 = Vertex.parseFromOBJ(tokens[2], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v2 = Vertex.parseFromOBJ(tokens[3], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
currentMaterial.addFace(face);
|
||||
currentMaterial.addFace(face2);
|
||||
} else if (tokens.length === 4) {
|
||||
// TRI
|
||||
const v0 = Vertex.parseFromOBJ(tokens[1], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v1 = Vertex.parseFromOBJ(tokens[2], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
const v2 = Vertex.parseFromOBJ(tokens[3], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
|
||||
|
||||
const face = new Triangle(v0, v1, v2);
|
||||
currentMaterial.addFace(face);
|
||||
} else {
|
||||
throw Error(`Unexpected number of face vertices, expected 3 or 4, got ${tokens.length - 1}`);
|
||||
}
|
||||
break;
|
||||
const face = new Triangle(v0, v1, v2);
|
||||
currentMaterial.addFace(face);
|
||||
} else {
|
||||
throw Error(`Unexpected number of face vertices, expected 3 or 4, got ${tokens.length - 1}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@ -322,7 +317,7 @@ export class Mesh {
|
||||
materialMap[currentMaterial.name] = currentMaterial;
|
||||
}
|
||||
|
||||
let materials: Array<Material> = [];
|
||||
const materials: Array<Material> = [];
|
||||
for (const material in materialMap) {
|
||||
materials.push(materialMap[material]);
|
||||
}
|
||||
@ -331,16 +326,16 @@ export class Mesh {
|
||||
|
||||
return {
|
||||
mtlPath: mtlPath,
|
||||
materials: materials
|
||||
}
|
||||
materials: materials,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private _centreMesh() {
|
||||
let centre = new Vector3(0, 0, 0);
|
||||
const centre = new Vector3(0, 0, 0);
|
||||
let totalWeight = 0;
|
||||
this.materials.forEach(material => {
|
||||
material.faces.forEach(face => {
|
||||
this.materials.forEach((material) => {
|
||||
material.faces.forEach((face) => {
|
||||
const k0 = Vector3.sub(face.v0.position, face.v1.position).magnitude();
|
||||
const k1 = Vector3.sub(face.v1.position, face.v2.position).magnitude();
|
||||
const k2 = Vector3.sub(face.v2.position, face.v0.position).magnitude();
|
||||
@ -352,8 +347,8 @@ export class Mesh {
|
||||
centre.divScalar(totalWeight);
|
||||
|
||||
// Translate each triangle
|
||||
this.materials.forEach(material => {
|
||||
material.faces.forEach(face => {
|
||||
this.materials.forEach((material) => {
|
||||
material.faces.forEach((face) => {
|
||||
face.v0.position = Vector3.sub(face.v0.position, centre);
|
||||
face.v1.position = Vector3.sub(face.v1.position, centre);
|
||||
face.v2.position = Vector3.sub(face.v2.position, centre);
|
||||
@ -367,11 +362,11 @@ export class Mesh {
|
||||
*/
|
||||
private _normaliseMesh() {
|
||||
// Find the size
|
||||
let a = new Vector3(Infinity, Infinity, Infinity);
|
||||
let b = new Vector3(-Infinity, -Infinity, -Infinity);
|
||||
const a = new Vector3(Infinity, Infinity, Infinity);
|
||||
const b = new Vector3(-Infinity, -Infinity, -Infinity);
|
||||
|
||||
this.materials.forEach(material => {
|
||||
material.faces.forEach(face => {
|
||||
this.materials.forEach((material) => {
|
||||
material.faces.forEach((face) => {
|
||||
const aabb = face.getBounds();
|
||||
a.x = Math.min(a.x, aabb.minX);
|
||||
a.y = Math.min(a.y, aabb.minY);
|
||||
@ -383,14 +378,14 @@ export class Mesh {
|
||||
});
|
||||
|
||||
const size = Vector3.sub(b, a);
|
||||
console.log("size", size);
|
||||
console.log('size', size);
|
||||
const targetSize = 8.0;
|
||||
const scaleFactor = targetSize / Math.max(size.x, size.y, size.z);
|
||||
console.log("scaleFactor", scaleFactor)
|
||||
console.log('scaleFactor', scaleFactor);
|
||||
|
||||
// Scale each triangle
|
||||
this.materials.forEach(material => {
|
||||
material.faces.forEach(face => {
|
||||
this.materials.forEach((material) => {
|
||||
material.faces.forEach((face) => {
|
||||
face.v0.position = Vector3.mulScalar(face.v0.position, scaleFactor);
|
||||
face.v1.position = Vector3.mulScalar(face.v1.position, scaleFactor);
|
||||
face.v2.position = Vector3.mulScalar(face.v2.position, scaleFactor);
|
||||
@ -399,14 +394,13 @@ export class Mesh {
|
||||
}
|
||||
|
||||
public loadTextures(gl: WebGLRenderingContext) {
|
||||
this.materials.forEach(material => {
|
||||
this.materials.forEach((material) => {
|
||||
if (material.materialData?.type === MaterialType.Texture) {
|
||||
material.materialData.texture = twgl.createTexture(gl, {
|
||||
src: material.materialData.texturePath,
|
||||
mag: gl.LINEAR
|
||||
mag: gl.LINEAR,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
10
src/mouse.ts
10
src/mouse.ts
@ -1,4 +1,4 @@
|
||||
import { Renderer } from "./renderer";
|
||||
import { Renderer } from './renderer';
|
||||
|
||||
interface MouseState {
|
||||
x: number,
|
||||
@ -7,7 +7,6 @@ interface MouseState {
|
||||
}
|
||||
|
||||
export class MouseManager {
|
||||
|
||||
private _gl: WebGLRenderingContext;
|
||||
|
||||
private static readonly MOUSE_LEFT = 1;
|
||||
@ -44,8 +43,8 @@ export class MouseManager {
|
||||
|
||||
public getMouseDelta() {
|
||||
const delta = {
|
||||
dx: this.currMouse.x - this.prevMouse.x,
|
||||
dy: -(this.currMouse.y - this.prevMouse.y)
|
||||
dx: this.currMouse.x - this.prevMouse.x,
|
||||
dy: -(this.currMouse.y - this.prevMouse.y),
|
||||
};
|
||||
this.prevMouse = this.currMouse;
|
||||
return delta;
|
||||
@ -56,5 +55,4 @@ export class MouseManager {
|
||||
const normY = -(2 * (this.currMouse.y / this._gl.canvas.height) - 1);
|
||||
return { x: normX, y: normY };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
23
src/ray.ts
23
src/ray.ts
@ -1,14 +1,13 @@
|
||||
import { Vector3 } from "./vector";
|
||||
import { Triangle } from "./triangle";
|
||||
import { floorToNearest, ceilToNearest, xAxis, yAxis, zAxis } from "./math";
|
||||
import { VoxelManager } from "./voxel_manager";
|
||||
import { Bounds } from "./util";
|
||||
import { Vector3 } from './vector';
|
||||
import { Bounds } from './util';
|
||||
|
||||
const EPSILON = 0.0000001;
|
||||
|
||||
/* eslint-disable */
|
||||
export enum Axes {
|
||||
x, y, z
|
||||
x, y, z,
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
interface Ray {
|
||||
origin: Vector3,
|
||||
@ -24,9 +23,9 @@ export function generateRays(v0: Vector3, v1: Vector3, v2: Vector3): Array<Ray>
|
||||
maxX: Math.ceil(Math.max(v0.x, v1.x, v2.x)),
|
||||
maxY: Math.ceil(Math.max(v0.y, v1.y, v2.y)),
|
||||
maxZ: Math.ceil(Math.max(v0.z, v1.z, v2.z)),
|
||||
}
|
||||
};
|
||||
|
||||
let rayList: Array<Ray> = [];
|
||||
const rayList: Array<Ray> = [];
|
||||
traverseX(rayList, bounds);
|
||||
traverseY(rayList, bounds);
|
||||
traverseZ(rayList, bounds);
|
||||
@ -39,7 +38,7 @@ function traverseX(rayList: Array<Ray>, bounds: Bounds) {
|
||||
rayList.push({
|
||||
origin: new Vector3(bounds.minX, y, z),
|
||||
direction: new Vector3(1, 0, 0),
|
||||
axis: Axes.x
|
||||
axis: Axes.x,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -51,7 +50,7 @@ function traverseY(rayList: Array<Ray>, bounds: Bounds) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, bounds.minY, z),
|
||||
direction: new Vector3(0, 1, 0),
|
||||
axis: Axes.y
|
||||
axis: Axes.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -63,7 +62,7 @@ function traverseZ(rayList: Array<Ray>, bounds: Bounds) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, y, bounds.minZ),
|
||||
direction: new Vector3(0, 0, 1),
|
||||
axis: Axes.z
|
||||
axis: Axes.z,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -100,4 +99,4 @@ export function rayIntersectTriangle(ray: Ray, v0: Vector3, v1: Vector3, v2: Vec
|
||||
if (t > EPSILON) {
|
||||
return Vector3.add(ray.origin, Vector3.mulScalar(ray.direction, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
117
src/renderer.ts
117
src/renderer.ts
@ -1,22 +1,20 @@
|
||||
import * as twgl from "twgl.js";
|
||||
import path from "path";
|
||||
import * as twgl from 'twgl.js';
|
||||
import path from 'path';
|
||||
|
||||
import { Vector3 } from "./vector";
|
||||
import { ArcballCamera } from "./camera";
|
||||
import { MouseManager } from "./mouse";
|
||||
import { ShaderManager } from "./shaders";
|
||||
import { BottomlessBuffer, SegmentedBuffer, VoxelData } from "./buffer";
|
||||
import { GeometryTemplates } from "./geometry";
|
||||
import { RGB, UV, rgbToArray } from "./util";
|
||||
import { VoxelManager } from "./voxel_manager";
|
||||
import { Triangle } from "./triangle";
|
||||
import { Mesh, FillMaterial, TextureMaterial, MaterialType } from "./mesh";
|
||||
import { FaceInfo, BlockAtlas } from "./block_atlas";
|
||||
import { AppConfig } from "./config"
|
||||
import { AppContext } from "./app_context";
|
||||
import { Vector3 } from './vector';
|
||||
import { ArcballCamera } from './camera';
|
||||
import { ShaderManager } from './shaders';
|
||||
import { BottomlessBuffer, SegmentedBuffer, VoxelData } from './buffer';
|
||||
import { GeometryTemplates } from './geometry';
|
||||
import { RGB, rgbToArray } from './util';
|
||||
import { VoxelManager } from './voxel_manager';
|
||||
import { Triangle } from './triangle';
|
||||
import { Mesh, FillMaterial, TextureMaterial, MaterialType } from './mesh';
|
||||
import { FaceInfo, BlockAtlas } from './block_atlas';
|
||||
import { AppConfig } from './config';
|
||||
import { AppContext } from './app_context';
|
||||
|
||||
export class Renderer {
|
||||
|
||||
public _gl: WebGLRenderingContext;
|
||||
|
||||
private _backgroundColour: RGB = {r: 0.1, g: 0.1, b: 0.1};
|
||||
@ -43,7 +41,7 @@ export class Renderer {
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
this._gl = (<HTMLCanvasElement>document.getElementById('canvas')).getContext("webgl")!;
|
||||
this._gl = (<HTMLCanvasElement>document.getElementById('canvas')).getContext('webgl')!;
|
||||
|
||||
this._getNewBuffers();
|
||||
this._setupOcclusions();
|
||||
@ -51,8 +49,8 @@ export class Renderer {
|
||||
this._materialBuffers = [];
|
||||
|
||||
this._atlasTexture = twgl.createTexture(this._gl, {
|
||||
src: path.join(__dirname, "../resources/blocks.png"),
|
||||
mag: this._gl.NEAREST
|
||||
src: path.join(__dirname, '../resources/blocks.png'),
|
||||
mag: this._gl.NEAREST,
|
||||
});
|
||||
}
|
||||
|
||||
@ -64,7 +62,7 @@ export class Renderer {
|
||||
this._debug = debug;
|
||||
}
|
||||
|
||||
public registerBox(centre: Vector3) { //, size: Vector3) {
|
||||
public registerBox(centre: Vector3) { // , size: Vector3) {
|
||||
const data = GeometryTemplates.getBoxBufferData(centre, this._debug);
|
||||
this._registerData(data);
|
||||
}
|
||||
@ -77,11 +75,11 @@ export class Renderer {
|
||||
new Vector3(1, 0, 0), new Vector3(-1, 0, 0),
|
||||
new Vector3(0, 1, 0), new Vector3(0, -1, 0),
|
||||
new Vector3(0, 0, 1), new Vector3(0, 0, -1),
|
||||
]
|
||||
];
|
||||
|
||||
private _calculateOcclusions(centre: Vector3) {
|
||||
const voxelManager = VoxelManager.Get;
|
||||
|
||||
const voxelManager = VoxelManager.Get;
|
||||
|
||||
// Cache local neighbours
|
||||
const localNeighbourhoodCache = Array<number>(27);
|
||||
for (let i = -1; i <= 1; ++i) {
|
||||
@ -94,7 +92,7 @@ export class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
let occlusions = new Array<Array<number>>(6);
|
||||
const occlusions = new Array<Array<number>>(6);
|
||||
// For each face
|
||||
for (let f = 0; f < 6; ++f) {
|
||||
occlusions[f] = [1, 1, 1, 1];
|
||||
@ -117,23 +115,22 @@ export class Renderer {
|
||||
if (numNeighbours == 2 && AppConfig.AMBIENT_OCCLUSION_OVERRIDE_CORNER) {
|
||||
++numNeighbours;
|
||||
} else {
|
||||
const neighbourIndex = this._occlusionNeighboursIndices[f][v][2];
|
||||
numNeighbours += localNeighbourhoodCache[neighbourIndex];
|
||||
const neighbourIndex = this._occlusionNeighboursIndices[f][v][2];
|
||||
numNeighbours += localNeighbourhoodCache[neighbourIndex];
|
||||
}
|
||||
|
||||
// Convert from occlusion denoting the occlusion factor to the
|
||||
// Convert from occlusion denoting the occlusion factor to the
|
||||
// attenuation in light value: 0 -> 1.0, 1 -> 0.8, 2 -> 0.6, 3 -> 0.4
|
||||
occlusions[f][v] = 1.0 - 0.2 * numNeighbours;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return occlusions;
|
||||
}
|
||||
|
||||
private static _getBlankOcclusions() {
|
||||
let blankOcclusions = new Array<Array<number>>(6);
|
||||
const blankOcclusions = new Array<Array<number>>(6);
|
||||
for (let f = 0; f < 6; ++f) {
|
||||
blankOcclusions[f] = [1, 1, 1, 1];
|
||||
}
|
||||
@ -157,7 +154,7 @@ export class Renderer {
|
||||
occlusions = Renderer._getBlankOcclusions();
|
||||
}
|
||||
|
||||
let data: VoxelData = GeometryTemplates.getBoxBufferData(centre, false);
|
||||
const data: VoxelData = GeometryTemplates.getBoxBufferData(centre, false);
|
||||
|
||||
// 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.
|
||||
@ -168,9 +165,9 @@ export class Renderer {
|
||||
data.occlusion[j * 16 + k] = occlusions[j][k % 4];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Assign the textures to each face
|
||||
const faceOrder = ["north", "south", "up", "down", "east", "west"];
|
||||
const faceOrder = ['north', 'south', 'up', 'down', 'east', 'west'];
|
||||
for (const face of faceOrder) {
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
const texcoord = blockTexcoord[face].texcoord;
|
||||
@ -178,8 +175,7 @@ export class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 6; ++i)
|
||||
{
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
if (!VoxelManager.Get.isVoxelAt(Vector3.add(centre, Renderer._faceNormals[i]))) {
|
||||
this._registerVoxels.add({
|
||||
position: data.position.slice(i * 12, (i+1) * 12),
|
||||
@ -189,7 +185,7 @@ export class Renderer {
|
||||
texcoord: data.texcoord.slice(i * 8, (i+1) * 8),
|
||||
blockTexcoord: data.blockTexcoord.slice(i * 8, (i+1) * 8),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,21 +195,21 @@ export class Renderer {
|
||||
}
|
||||
|
||||
public registerMesh(mesh: Mesh) {
|
||||
mesh.materials.forEach(material => {
|
||||
mesh.materials.forEach((material) => {
|
||||
const materialBuffer = new BottomlessBuffer([
|
||||
{ name: 'position', numComponents: 3 },
|
||||
{ name: 'texcoord', numComponents: 2 },
|
||||
{ name: 'normal', numComponents: 3 }
|
||||
{ name: 'normal', numComponents: 3 },
|
||||
]);
|
||||
|
||||
material.faces.forEach(face => {
|
||||
material.faces.forEach((face) => {
|
||||
const data = GeometryTemplates.getTriangleBufferData(face, false);
|
||||
materialBuffer.add(data);
|
||||
});
|
||||
|
||||
this._materialBuffers.push({
|
||||
buffer: materialBuffer,
|
||||
material: material.materialData
|
||||
material: material.materialData,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -233,7 +229,7 @@ export class Renderer {
|
||||
// Setup arrays for calculating voxel ambient occlusion
|
||||
for (let i = 0; i < voxelManager.voxels.length; ++i) {
|
||||
const voxel = voxelManager.voxels[i];
|
||||
//const colour = voxelManager.voxelColours[i];
|
||||
// const colour = voxelManager.voxelColours[i];
|
||||
const texcoord = voxelManager.voxelTexcoords[i];
|
||||
this._registerVoxel(voxel.position, texcoord);
|
||||
}
|
||||
@ -275,8 +271,8 @@ export class Renderer {
|
||||
this._drawRegister(this._registerVoxels, this._gl.TRIANGLES, ShaderManager.Get.aoProgram, {
|
||||
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
|
||||
u_texture: this._atlasTexture,
|
||||
u_voxelSize: VoxelManager.Get._voxelSize,
|
||||
u_atlasSize: this._atlasSize
|
||||
u_voxelSize: VoxelManager.Get.voxelSize,
|
||||
u_atlasSize: this._atlasSize,
|
||||
});
|
||||
|
||||
// Draw material registers
|
||||
@ -287,14 +283,14 @@ export class Renderer {
|
||||
u_lightWorldPos: camera.getCameraPosition(0.0, 0.0),
|
||||
u_worldViewProjection: camera.getWorldViewProjection(),
|
||||
u_worldInverseTranspose: camera.getWorldInverseTranspose(),
|
||||
u_texture: materialBuffer.material.texture
|
||||
u_texture: materialBuffer.material.texture,
|
||||
});
|
||||
} else {
|
||||
this._drawRegister(materialBuffer.buffer, this._gl.TRIANGLES, ShaderManager.Get.shadedFillProgram, {
|
||||
u_lightWorldPos: camera.getCameraPosition(0.0, 0.0),
|
||||
u_worldViewProjection: camera.getWorldViewProjection(),
|
||||
u_worldInverseTranspose: camera.getWorldInverseTranspose(),
|
||||
u_fillColour: rgbToArray(materialBuffer.material.diffuseColour)
|
||||
u_fillColour: rgbToArray(materialBuffer.material.diffuseColour),
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -307,7 +303,7 @@ export class Renderer {
|
||||
}
|
||||
|
||||
_setupOcclusions() {
|
||||
// TODO: Find some for-loop to clean this up
|
||||
// TODO: Find some for-loop to clean this up
|
||||
|
||||
// [Edge, Edge, Corrner]
|
||||
const occlusionNeighbours = [
|
||||
@ -316,7 +312,7 @@ export class Renderer {
|
||||
[new Vector3(1, 1, 0), new Vector3(1, 0, -1), new Vector3(1, 1, -1)],
|
||||
[new Vector3(1, -1, 0), new Vector3(1, 0, -1), new Vector3(1, -1, -1)],
|
||||
[new Vector3(1, 1, 0), new Vector3(1, 0, 1), new Vector3(1, 1, 1)],
|
||||
[new Vector3(1, -1, 0), new Vector3(1, 0, 1), new Vector3(1, -1, 1)]
|
||||
[new Vector3(1, -1, 0), new Vector3(1, 0, 1), new Vector3(1, -1, 1)],
|
||||
],
|
||||
|
||||
[
|
||||
@ -324,7 +320,7 @@ export class Renderer {
|
||||
[new Vector3(-1, 1, 0), new Vector3(-1, 0, 1), new Vector3(-1, 1, 1)],
|
||||
[new Vector3(-1, -1, 0), new Vector3(-1, 0, 1), new Vector3(-1, -1, 1)],
|
||||
[new Vector3(-1, 1, 0), new Vector3(-1, 0, -1), new Vector3(-1, 1, -1)],
|
||||
[new Vector3(-1, -1, 0), new Vector3(-1, 0, -1), new Vector3(-1, -1, -1) ]
|
||||
[new Vector3(-1, -1, 0), new Vector3(-1, 0, -1), new Vector3(-1, -1, -1)],
|
||||
],
|
||||
|
||||
[
|
||||
@ -332,7 +328,7 @@ export class Renderer {
|
||||
[new Vector3(-1, 1, 0), new Vector3(0, 1, 1), new Vector3(-1, 1, 1)],
|
||||
[new Vector3(-1, 1, 0), new Vector3(0, 1, -1), new Vector3(-1, 1, -1)],
|
||||
[new Vector3(1, 1, 0), new Vector3(0, 1, 1), new Vector3(1, 1, 1)],
|
||||
[new Vector3(1, 1, 0), new Vector3(0, 1, -1), new Vector3(1, 1, -1)]
|
||||
[new Vector3(1, 1, 0), new Vector3(0, 1, -1), new Vector3(1, 1, -1)],
|
||||
],
|
||||
|
||||
[
|
||||
@ -340,7 +336,7 @@ export class Renderer {
|
||||
[new Vector3(-1, -1, 0), new Vector3(0, -1, -1), new Vector3(-1, -1, -1)],
|
||||
[new Vector3(-1, -1, 0), new Vector3(0, -1, 1), new Vector3(-1, -1, 1)],
|
||||
[new Vector3(1, -1, 0), new Vector3(0, -1, -1), new Vector3(1, -1, -1)],
|
||||
[new Vector3(1, -1, 0), new Vector3(0, -1, 1), new Vector3(1, -1, 1)]
|
||||
[new Vector3(1, -1, 0), new Vector3(0, -1, 1), new Vector3(1, -1, 1)],
|
||||
],
|
||||
|
||||
[
|
||||
@ -348,7 +344,7 @@ export class Renderer {
|
||||
[new Vector3(0, 1, 1), new Vector3(1, 0, 1), new Vector3(1, 1, 1)],
|
||||
[new Vector3(0, -1, 1), new Vector3(1, 0, 1), new Vector3(1, -1, 1)],
|
||||
[new Vector3(0, 1, 1), new Vector3(-1, 0, 1), new Vector3(-1, 1, 1)],
|
||||
[new Vector3(0, -1, 1), new Vector3(-1, 0, 1), new Vector3(-1, -1, 1)]
|
||||
[new Vector3(0, -1, 1), new Vector3(-1, 0, 1), new Vector3(-1, -1, 1)],
|
||||
],
|
||||
|
||||
[
|
||||
@ -356,15 +352,15 @@ export class Renderer {
|
||||
[new Vector3(0, 1, -1), new Vector3(-1, 0, -1), new Vector3(-1, 1, -1)],
|
||||
[new Vector3(0, -1, -1), new Vector3(-1, 0, -1), new Vector3(-1, -1, -1)],
|
||||
[new Vector3(0, 1, -1), new Vector3(1, 0, -1), new Vector3(1, 1, -1)],
|
||||
[new Vector3(0, -1, -1), new Vector3(1, 0, -1), new Vector3(1, -1, -1)]
|
||||
]
|
||||
]
|
||||
[new Vector3(0, -1, -1), new Vector3(1, 0, -1), new Vector3(1, -1, -1)],
|
||||
],
|
||||
];
|
||||
|
||||
this._occlusionNeighboursIndices = new Array<Array<Array<number>>>();
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
let row = new Array<Array<number>>();
|
||||
const row = new Array<Array<number>>();
|
||||
for (let j = 0; j < 4; ++j) {
|
||||
row.push(occlusionNeighbours[i][j].map(x => Renderer._getNeighbourIndex(x)));
|
||||
row.push(occlusionNeighbours[i][j].map((x) => Renderer._getNeighbourIndex(x)));
|
||||
}
|
||||
this._occlusionNeighboursIndices.push(row);
|
||||
}
|
||||
@ -382,7 +378,7 @@ export class Renderer {
|
||||
}
|
||||
|
||||
_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);
|
||||
@ -404,7 +400,7 @@ export class Renderer {
|
||||
const bufferSize = 16384 * 16;
|
||||
this._registerDebug = new SegmentedBuffer(bufferSize, [
|
||||
{ name: 'position', numComponents: 3, insertIndex: 0 },
|
||||
{ name: 'colour', numComponents: 3, insertIndex: 0 }
|
||||
{ name: 'colour', numComponents: 3, insertIndex: 0 },
|
||||
]);
|
||||
this._registerVoxels = new SegmentedBuffer(bufferSize, [
|
||||
{ name: 'position', numComponents: 3, insertIndex: 0 },
|
||||
@ -415,11 +411,10 @@ export class Renderer {
|
||||
]);
|
||||
this._registerDefault = new SegmentedBuffer(bufferSize, [
|
||||
{ name: 'position', numComponents: 3, insertIndex: 0 },
|
||||
//{name: 'colour', numComponents: 3},
|
||||
{ name: 'normal', numComponents: 3, insertIndex: 0 }
|
||||
// {name: 'colour', numComponents: 3},
|
||||
{ name: 'normal', numComponents: 3, insertIndex: 0 },
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports.Renderer = Renderer;
|
||||
module.exports.Renderer = Renderer;
|
||||
|
108
src/schematic.ts
108
src/schematic.ts
@ -1,18 +1,17 @@
|
||||
import * as zlib from "zlib";
|
||||
import * as fs from "fs";
|
||||
import { NBT, TagType, writeUncompressed } from "prismarine-nbt";
|
||||
import { Vector3 } from "./vector";
|
||||
import { VoxelManager } from "./voxel_manager";
|
||||
import { Block } from "./block_atlas";
|
||||
import * as zlib from 'zlib';
|
||||
import * as fs from 'fs';
|
||||
import { NBT, TagType, writeUncompressed } from 'prismarine-nbt';
|
||||
import { Vector3 } from './vector';
|
||||
import { VoxelManager } from './voxel_manager';
|
||||
import { Block } from './block_atlas';
|
||||
|
||||
export abstract class Exporter {
|
||||
protected _sizeVector!: Vector3;
|
||||
|
||||
protected _sizeVector!: Vector3
|
||||
|
||||
abstract convertToNBT(): NBT
|
||||
public abstract convertToNBT(): NBT
|
||||
abstract getFormatFilter(): Electron.FileFilter;
|
||||
abstract getFormatName(): string;
|
||||
|
||||
|
||||
getFormatDisclaimer(): string | undefined {
|
||||
return;
|
||||
}
|
||||
@ -23,7 +22,7 @@ export abstract class Exporter {
|
||||
const nbt = this.convertToNBT();
|
||||
|
||||
const outBuffer = fs.createWriteStream(filePath);
|
||||
const newBuffer = writeUncompressed(nbt, "big");
|
||||
const newBuffer = writeUncompressed(nbt, 'big');
|
||||
|
||||
zlib.gzip(newBuffer, (err, buffer) => {
|
||||
if (!err) {
|
||||
@ -35,17 +34,14 @@ export abstract class Exporter {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Schematic extends Exporter {
|
||||
|
||||
convertToNBT() {
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
|
||||
let blocksData = Array<number>(bufferSize);
|
||||
VoxelManager.Get.voxels.forEach(voxel => {
|
||||
const blocksData = Array<number>(bufferSize);
|
||||
VoxelManager.Get.voxels.forEach((voxel) => {
|
||||
const indexVector = Vector3.sub(voxel.position, VoxelManager.Get.min);
|
||||
const index = this._getBufferIndex(indexVector, this._sizeVector);
|
||||
blocksData[index] = Block.Stone;
|
||||
@ -62,8 +58,8 @@ export class Schematic extends Exporter {
|
||||
Blocks: { type: TagType.ByteArray, value: blocksData },
|
||||
Data: { type: TagType.ByteArray, value: new Array<number>(bufferSize).fill(0) },
|
||||
Entities: { type: TagType.List, value: { type: TagType.Int, value: Array(0) } },
|
||||
TileEntities: { type: TagType.List, value: { type: TagType.Int, value: Array(0) } }
|
||||
}
|
||||
TileEntities: { type: TagType.List, value: { type: TagType.Int, value: Array(0) } },
|
||||
},
|
||||
};
|
||||
|
||||
return nbt;
|
||||
@ -76,18 +72,17 @@ export class Schematic extends Exporter {
|
||||
getFormatFilter() {
|
||||
return {
|
||||
name: this.getFormatName(),
|
||||
extensions: ['schematic']
|
||||
}
|
||||
extensions: ['schematic'],
|
||||
};
|
||||
}
|
||||
|
||||
getFormatName() {
|
||||
return "Schematic";
|
||||
return 'Schematic';
|
||||
}
|
||||
|
||||
getFormatDisclaimer() {
|
||||
return "Schematic files only support pre-1.13 blocks. As a result, all blocks will be exported as Stone. To export the blocks, use the .litematic format with the Litematica mod.";
|
||||
return 'Schematic files only support pre-1.13 blocks. As a result, all blocks will be exported as Stone. To export the blocks, use the .litematic format with the Litematica mod.';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type BlockID = number;
|
||||
@ -98,7 +93,6 @@ interface BlockMapping {
|
||||
}
|
||||
|
||||
export class Litematic extends Exporter {
|
||||
|
||||
// XZY
|
||||
_getBufferIndex(vec: Vector3) {
|
||||
return (this._sizeVector.z * this._sizeVector.x * vec.y) + (this._sizeVector.x * vec.z) + vec.x;
|
||||
@ -106,8 +100,8 @@ export class Litematic extends Exporter {
|
||||
|
||||
_createBlockMapping(): BlockMapping {
|
||||
const blockPalette = VoxelManager.Get.blockPalette;
|
||||
|
||||
let blockMapping: BlockMapping = {"air": 0};
|
||||
|
||||
const blockMapping: BlockMapping = { 'air': 0 };
|
||||
for (let i = 0; i < blockPalette.length; ++i) {
|
||||
const blockName = blockPalette[i];
|
||||
blockMapping[blockName] = i + 1; // Ensure 0 maps to air
|
||||
@ -119,11 +113,11 @@ export class Litematic extends Exporter {
|
||||
_createBlockBuffer(blockMapping: BlockMapping): Array<BlockID> {
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
|
||||
let buffer = Array<BlockID>(bufferSize).fill(0);
|
||||
VoxelManager.Get.voxels.forEach(voxel => {
|
||||
const buffer = Array<BlockID>(bufferSize).fill(0);
|
||||
VoxelManager.Get.voxels.forEach((voxel) => {
|
||||
const indexVector = Vector3.sub(voxel.position, VoxelManager.Get.min);
|
||||
const index = this._getBufferIndex(indexVector);
|
||||
buffer[index] = blockMapping[voxel.block || "air"];
|
||||
buffer[index] = blockMapping[voxel.block || 'air'];
|
||||
});
|
||||
|
||||
return buffer;
|
||||
@ -132,7 +126,7 @@ export class Litematic extends Exporter {
|
||||
_createBlockStates(blockMapping: BlockMapping) {
|
||||
const blockEncoding = this._encodeBlockBuffer(blockMapping);
|
||||
|
||||
let blockStates = new Array<long>();
|
||||
const blockStates = new Array<long>();
|
||||
|
||||
for (let i = blockEncoding.length; i > 0; i -= 64) {
|
||||
let right = parseInt(blockEncoding.substring(i-32, i), 2);
|
||||
@ -140,55 +134,52 @@ export class Litematic extends Exporter {
|
||||
|
||||
// TODO: Cleanup, UINT32 -> INT32
|
||||
if (right > 2147483647) {
|
||||
//right = -(-right & 0xFFFFFFFF);
|
||||
right -= 4294967296;
|
||||
}
|
||||
if (left > 2147483647) {
|
||||
//left = -(-left & 0xFFFFFFFF);
|
||||
left -= 4294967296;
|
||||
}
|
||||
|
||||
blockStates.push([left, right]);
|
||||
}
|
||||
|
||||
|
||||
return blockStates;
|
||||
}
|
||||
|
||||
_encodeBlockBuffer(blockMapping: BlockMapping) {
|
||||
let blockBuffer = this._createBlockBuffer(blockMapping);
|
||||
const blockBuffer = this._createBlockBuffer(blockMapping);
|
||||
|
||||
const paletteSize = Object.keys(blockMapping).length;
|
||||
let stride = (paletteSize - 1).toString(2).length;
|
||||
stride = Math.max(2, stride);
|
||||
|
||||
let encoding = "";
|
||||
let encoding = '';
|
||||
for (let i = blockBuffer.length - 1; i >= 0; --i) {
|
||||
encoding += blockBuffer[i].toString(2).padStart(stride, "0");
|
||||
encoding += blockBuffer[i].toString(2).padStart(stride, '0');
|
||||
}
|
||||
|
||||
const requiredLength = Math.ceil(encoding.length / 64) * 64;
|
||||
encoding = encoding.padStart(requiredLength, "0");
|
||||
encoding = encoding.padStart(requiredLength, '0');
|
||||
|
||||
return encoding;
|
||||
}
|
||||
|
||||
_createBlockStatePalette(blockMapping: BlockMapping) {
|
||||
let blockStatePalette = Array(Object.keys(blockMapping).length);
|
||||
const blockStatePalette = Array(Object.keys(blockMapping).length);
|
||||
for (const block of Object.keys(blockMapping)) {
|
||||
const index = blockMapping[block];
|
||||
const blockName = "minecraft:" + block;
|
||||
const blockName = 'minecraft:' + block;
|
||||
blockStatePalette[index] = { Name: { type: TagType.String, value: blockName } };
|
||||
}
|
||||
blockStatePalette[0] = { Name: { type: TagType.String, value: "minecraft:air" } };
|
||||
blockStatePalette[0] = { Name: { type: TagType.String, value: 'minecraft:air' } };
|
||||
|
||||
return blockStatePalette;
|
||||
}
|
||||
|
||||
convertToNBT() {
|
||||
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
const blockMapping = this._createBlockMapping();
|
||||
|
||||
|
||||
const blockStates = this._createBlockStates(blockMapping);
|
||||
const blockStatePalette = this._createBlockStatePalette(blockMapping);
|
||||
|
||||
@ -198,16 +189,16 @@ export class Litematic extends Exporter {
|
||||
value: {
|
||||
Metadata: {
|
||||
type: TagType.Compound, value: {
|
||||
Author: { type: TagType.String, value: "" },
|
||||
Description: { type: TagType.String, value: "" },
|
||||
Author: { type: TagType.String, value: '' },
|
||||
Description: { type: TagType.String, value: '' },
|
||||
Size: {
|
||||
type: TagType.Compound, value: {
|
||||
x: { type: TagType.Int, value: this._sizeVector.x },
|
||||
y: { type: TagType.Int, value: this._sizeVector.y },
|
||||
z: { type: TagType.Int, value: this._sizeVector.z },
|
||||
}
|
||||
},
|
||||
},
|
||||
Name: { type: TagType.String, value: "" },
|
||||
Name: { type: TagType.String, value: '' },
|
||||
RegionCount: { type: TagType.Int, value: 1 },
|
||||
TimeCreated: { type: TagType.Long, value: [0, 0] },
|
||||
TimeModified: { type: TagType.Long, value: [0, 0] },
|
||||
@ -226,7 +217,7 @@ export class Litematic extends Exporter {
|
||||
x: { type: TagType.Int, value: 0 },
|
||||
y: { type: TagType.Int, value: 0 },
|
||||
z: { type: TagType.Int, value: 0 },
|
||||
}
|
||||
},
|
||||
},
|
||||
BlockStatePalette: { type: TagType.List, value: { type: TagType.Compound, value: blockStatePalette } },
|
||||
Size: {
|
||||
@ -234,32 +225,31 @@ export class Litematic extends Exporter {
|
||||
x: { type: TagType.Int, value: this._sizeVector.x },
|
||||
y: { type: TagType.Int, value: this._sizeVector.y },
|
||||
z: { type: TagType.Int, value: this._sizeVector.z },
|
||||
}
|
||||
},
|
||||
},
|
||||
PendingFluidTicks: { type: TagType.List, value: { type: TagType.Int, value: [] } },
|
||||
TileEntities: { type: TagType.List, value: { type: TagType.Int, value: [] } },
|
||||
Entities: { type: TagType.List, value: { type: TagType.Int, value: [] } }
|
||||
}
|
||||
}
|
||||
Entities: { type: TagType.List, value: { type: TagType.Int, value: [] } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MinecraftDataVersion: { type: TagType.Int, value: 2730 },
|
||||
Version: { type: TagType.Int, value: 5 }
|
||||
}
|
||||
Version: { type: TagType.Int, value: 5 },
|
||||
},
|
||||
};
|
||||
|
||||
return nbt;
|
||||
}
|
||||
|
||||
|
||||
getFormatFilter() {
|
||||
return {
|
||||
name: this.getFormatName(),
|
||||
extensions: ['litematic']
|
||||
}
|
||||
extensions: ['litematic'],
|
||||
};
|
||||
}
|
||||
|
||||
getFormatName() {
|
||||
return "Litematic";
|
||||
return 'Litematic';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
import * as twgl from "twgl.js";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Renderer } from "./renderer";
|
||||
|
||||
import * as twgl from 'twgl.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Renderer } from './renderer';
|
||||
|
||||
export class ShaderManager {
|
||||
|
||||
public readonly shadedTextureProgram: twgl.ProgramInfo;
|
||||
public readonly shadedFillProgram: twgl.ProgramInfo;
|
||||
public readonly debugProgram: twgl.ProgramInfo;
|
||||
@ -27,7 +25,7 @@ export class ShaderManager {
|
||||
const shadedVertexFillShader = this._getShader('shaded_vertex_fill.vs');
|
||||
const shadedFragmentFillShader = this._getShader('shaded_fragment_fill.fs');
|
||||
this.shadedFillProgram = twgl.createProgramInfo(gl, [shadedVertexFillShader, shadedFragmentFillShader]);
|
||||
|
||||
|
||||
const debugVertexShader = this._getShader('debug_vertex.vs');
|
||||
const debugFragmentShader = this._getShader('debug_fragment.fs');
|
||||
this.debugProgram = twgl.createProgramInfo(gl, [debugVertexShader, debugFragmentShader]);
|
||||
@ -41,5 +39,4 @@ export class ShaderManager {
|
||||
const absPath = path.join(__dirname, '../shaders/' + filename);
|
||||
return fs.readFileSync(absPath, 'utf8');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +1,52 @@
|
||||
import * as fs from "fs";
|
||||
import * as jpeg from "jpeg-js";
|
||||
import { PNG } from "pngjs";
|
||||
import { UV, RGBA } from "./util";
|
||||
import * as fs from 'fs';
|
||||
import * as jpeg from 'jpeg-js';
|
||||
import { PNG } from 'pngjs';
|
||||
import { UV, RGBA } from './util';
|
||||
|
||||
/* eslint-disable */
|
||||
export enum TextureFormat {
|
||||
PNG,
|
||||
JPEG
|
||||
PNG,
|
||||
JPEG
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
export class Texture {
|
||||
private _image: {
|
||||
data: Buffer,
|
||||
width: number,
|
||||
height: number
|
||||
};
|
||||
|
||||
private _image: {
|
||||
data: Buffer,
|
||||
width: number,
|
||||
height: number
|
||||
};
|
||||
constructor(filename: string, format: TextureFormat) {
|
||||
try {
|
||||
const data = fs.readFileSync(filename);
|
||||
if (format === TextureFormat.PNG) {
|
||||
this._image = PNG.sync.read(data);
|
||||
} else {
|
||||
this._image = jpeg.decode(data);
|
||||
}
|
||||
if (this._image.width * this._image.height * 4 !== this._image.data.length) {
|
||||
throw Error();
|
||||
}
|
||||
} catch (err) {
|
||||
throw Error(`Could not parse ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(filename: string, format: TextureFormat) {
|
||||
try {
|
||||
const data = fs.readFileSync(filename);
|
||||
if (format === TextureFormat.PNG) {
|
||||
this._image = PNG.sync.read(data);
|
||||
} else {
|
||||
this._image = jpeg.decode(data);
|
||||
}
|
||||
if (this._image.width * this._image.height * 4 !== this._image.data.length) {
|
||||
throw Error();
|
||||
}
|
||||
} catch (err) {
|
||||
throw Error(`Could not parse ${filename}`);
|
||||
}
|
||||
}
|
||||
getRGBA(uv: UV): RGBA {
|
||||
uv.v = 1 - uv.v;
|
||||
|
||||
getRGBA(uv: UV): RGBA {
|
||||
uv.v = 1 - uv.v;
|
||||
const x = Math.floor(uv.u * this._image.width);
|
||||
const y = Math.floor(uv.v * this._image.height);
|
||||
|
||||
const x = Math.floor(uv.u * this._image.width);
|
||||
const y = Math.floor(uv.v * this._image.height);
|
||||
const index = 4 * (this._image.width * y + x);
|
||||
const rgba = this._image.data.slice(index, index + 4);
|
||||
|
||||
const index = 4 * (this._image.width * y + x);
|
||||
const rgba = this._image.data.slice(index, index + 4)
|
||||
|
||||
return {
|
||||
r: rgba[0]/255,
|
||||
g: rgba[1]/255,
|
||||
b: rgba[2]/255,
|
||||
a: 1.0
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: rgba[0] / 255,
|
||||
g: rgba[1] / 255,
|
||||
b: rgba[2] / 255,
|
||||
a: 1.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { Vector3 } from "./vector";
|
||||
import { Vertex } from "./mesh";
|
||||
import { Bounds } from "./util";
|
||||
|
||||
|
||||
import { Vector3 } from './vector';
|
||||
import { Vertex } from './mesh';
|
||||
import { Bounds } from './util';
|
||||
export class Triangle {
|
||||
|
||||
public v0: Vertex;
|
||||
public v1: Vertex;
|
||||
public v2: Vertex;
|
||||
@ -32,7 +29,6 @@ export class Triangle {
|
||||
maxX: Math.max(this.v0.position.x, this.v1.position.x, this.v2.position.x),
|
||||
maxY: Math.max(this.v0.position.y, this.v1.position.y, this.v2.position.y),
|
||||
maxZ: Math.max(this.v0.position.z, this.v1.position.z, this.v2.position.z),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
10
src/util.ts
10
src/util.ts
@ -16,8 +16,10 @@ export interface RGB {
|
||||
}
|
||||
|
||||
export function getAverageColour(colours: Array<RGB>) {
|
||||
let averageColour = colours.reduce((a, c) => { return { r: a.r + c.r, g: a.g + c.g, b: a.b + c.b } });
|
||||
let n = colours.length;
|
||||
const averageColour = colours.reduce((a, c) => {
|
||||
return { r: a.r + c.r, g: a.g + c.g, b: a.b + c.b };
|
||||
});
|
||||
const n = colours.length;
|
||||
averageColour.r /= n;
|
||||
averageColour.g /= n;
|
||||
averageColour.b /= n;
|
||||
@ -47,8 +49,8 @@ export interface Bounds {
|
||||
maxZ: number,
|
||||
}
|
||||
|
||||
export function assert(condition: boolean, errorMessage: string = "Assertion Failed") {
|
||||
export function assert(condition: boolean, errorMessage = 'Assertion Failed') {
|
||||
if (!condition) {
|
||||
throw Error(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Hashable } from "./hash_map";
|
||||
import { Hashable } from './hash_map';
|
||||
|
||||
export class Vector3 extends Hashable {
|
||||
|
||||
public x: number;
|
||||
public y: number;
|
||||
public z: number;
|
||||
@ -21,17 +20,19 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
vecA.x + vecB.x,
|
||||
vecA.y + vecB.y,
|
||||
vecA.z + vecB.z
|
||||
vecA.z + vecB.z,
|
||||
);
|
||||
}
|
||||
|
||||
static parse(line: string) {
|
||||
var regex = /[+-]?\d+(\.\d+)?/g;
|
||||
var floats = line.match(regex)!.map(function(v) { return parseFloat(v); });
|
||||
const regex = /[+-]?\d+(\.\d+)?/g;
|
||||
const floats = line.match(regex)!.map(function(v) {
|
||||
return parseFloat(v);
|
||||
});
|
||||
|
||||
return new Vector3(
|
||||
floats[0], floats[1], floats[2]
|
||||
)
|
||||
floats[0], floats[1], floats[2],
|
||||
);
|
||||
}
|
||||
|
||||
add(vec: Vector3) {
|
||||
@ -45,7 +46,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
vec.x + scalar,
|
||||
vec.y + scalar,
|
||||
vec.z + scalar
|
||||
vec.z + scalar,
|
||||
);
|
||||
}
|
||||
|
||||
@ -60,7 +61,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
vecA.x - vecB.x,
|
||||
vecA.y - vecB.y,
|
||||
vecA.z - vecB.z
|
||||
vecA.z - vecB.z,
|
||||
);
|
||||
}
|
||||
|
||||
@ -75,7 +76,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
vec.x - scalar,
|
||||
vec.y - scalar,
|
||||
vec.z - scalar
|
||||
vec.z - scalar,
|
||||
);
|
||||
}
|
||||
|
||||
@ -87,7 +88,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
vec.x,
|
||||
vec.y,
|
||||
vec.z
|
||||
vec.z,
|
||||
);
|
||||
}
|
||||
|
||||
@ -95,7 +96,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
this.x,
|
||||
this.y,
|
||||
this.z
|
||||
this.z,
|
||||
);
|
||||
}
|
||||
|
||||
@ -103,7 +104,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
scalar * vec.x,
|
||||
scalar * vec.y,
|
||||
scalar * vec.z
|
||||
scalar * vec.z,
|
||||
);
|
||||
}
|
||||
|
||||
@ -118,7 +119,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
vec.x / scalar,
|
||||
vec.y / scalar,
|
||||
vec.z / scalar
|
||||
vec.z / scalar,
|
||||
);
|
||||
}
|
||||
|
||||
@ -137,7 +138,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
Math.round(vec.x),
|
||||
Math.round(vec.y),
|
||||
Math.round(vec.z)
|
||||
Math.round(vec.z),
|
||||
);
|
||||
}
|
||||
|
||||
@ -152,7 +153,7 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
Math.abs(vec.x),
|
||||
Math.abs(vec.y),
|
||||
Math.abs(vec.z)
|
||||
Math.abs(vec.z),
|
||||
);
|
||||
}
|
||||
|
||||
@ -160,25 +161,23 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(
|
||||
vecA.y * vecB.z - vecA.z * vecB.y,
|
||||
vecA.z * vecB.x - vecA.x * vecB.z,
|
||||
vecA.x * vecB.y - vecA.y * vecB.x
|
||||
vecA.x * vecB.y - vecA.y * vecB.x,
|
||||
);
|
||||
}
|
||||
|
||||
/** Performs element-wise min */
|
||||
static min(vecA: Vector3, vecB: Vector3) {
|
||||
return new Vector3(
|
||||
Math.min(vecA.x, vecB.x),
|
||||
Math.min(vecA.y, vecB.y),
|
||||
Math.min(vecA.z, vecB.z)
|
||||
Math.min(vecA.z, vecB.z),
|
||||
);
|
||||
}
|
||||
|
||||
/** Performs element-wise max */
|
||||
static max(vecA: Vector3, vecB: Vector3) {
|
||||
return new Vector3(
|
||||
Math.max(vecA.x, vecB.x),
|
||||
Math.max(vecA.y, vecB.y),
|
||||
Math.max(vecA.z, vecB.z)
|
||||
Math.max(vecA.z, vecB.z),
|
||||
);
|
||||
}
|
||||
|
||||
@ -186,7 +185,7 @@ export class Vector3 extends Hashable {
|
||||
const p0 = 73856093;
|
||||
const p1 = 19349663;
|
||||
const p2 = 83492791;
|
||||
return (this.x * p0) ^ (this.y * p1) ^ (this.z * p2);
|
||||
return (this.x * p0) ^ (this.y * p1) ^ (this.z * p2);
|
||||
}
|
||||
|
||||
equals(vec: Vector3) {
|
||||
@ -205,5 +204,4 @@ export class Vector3 extends Hashable {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Vector3 } from "./vector.js";
|
||||
import { HashMap } from "./hash_map";
|
||||
import { Texture } from "./texture";
|
||||
import { BlockAtlas, BlockInfo, FaceInfo } from "./block_atlas";
|
||||
import { RGB, getAverageColour } from "./util";
|
||||
import { Triangle } from "./triangle";
|
||||
import { Mesh, MaterialType } from "./mesh";
|
||||
import { triangleArea } from "./math";
|
||||
import { Axes, generateRays, rayIntersectTriangle } from "./ray";
|
||||
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from "./block_assigner.js";
|
||||
import { AppContext } from "./app_context.js";
|
||||
import { Vector3 } from './vector.js';
|
||||
import { HashMap } from './hash_map';
|
||||
import { Texture } from './texture';
|
||||
import { BlockAtlas, BlockInfo, FaceInfo } from './block_atlas';
|
||||
import { RGB, getAverageColour } from './util';
|
||||
import { Triangle } from './triangle';
|
||||
import { Mesh, MaterialType } from './mesh';
|
||||
import { triangleArea } from './math';
|
||||
import { Axes, generateRays, rayIntersectTriangle } from './ray';
|
||||
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from './block_assigner.js';
|
||||
import { AppContext } from './app_context.js';
|
||||
|
||||
interface Block {
|
||||
position: Vector3;
|
||||
@ -18,19 +18,17 @@ interface Block {
|
||||
}
|
||||
|
||||
export class VoxelManager {
|
||||
|
||||
public voxels: Array<Block>;
|
||||
public voxelTexcoords: Array<FaceInfo>;
|
||||
public _voxelSize: number;
|
||||
public voxelSize: number;
|
||||
public blockPalette: Array<string>;
|
||||
public min = new Vector3( Infinity, Infinity, Infinity);
|
||||
public max = new Vector3(-Infinity, -Infinity, -Infinity);
|
||||
|
||||
private voxelsHash: HashMap<Vector3, Block>;
|
||||
private _voxelsHash: HashMap<Vector3, Block>;
|
||||
private _blockMode!: MaterialType;
|
||||
private _currentTexture!: Texture;
|
||||
private _currentColour!: RGB;
|
||||
public blockPalette: Array<string>;
|
||||
|
||||
public min = new Vector3( Infinity, Infinity, Infinity);
|
||||
public max = new Vector3(-Infinity, -Infinity, -Infinity);
|
||||
|
||||
private static _instance: VoxelManager;
|
||||
|
||||
@ -39,32 +37,32 @@ export class VoxelManager {
|
||||
}
|
||||
|
||||
private constructor(voxelSize: number) {
|
||||
this._voxelSize = voxelSize;
|
||||
this.voxelSize = voxelSize;
|
||||
this.voxels = [];
|
||||
this.voxelTexcoords = [];
|
||||
|
||||
this.voxelsHash = new HashMap(2048);
|
||||
this._voxelsHash = new HashMap(2048);
|
||||
this.blockPalette = [];
|
||||
}
|
||||
|
||||
public setVoxelSize(voxelSize: number) {
|
||||
this._voxelSize = voxelSize;
|
||||
this.voxelSize = voxelSize;
|
||||
}
|
||||
|
||||
private _clearVoxels() {
|
||||
this.voxels = [];
|
||||
this.voxelTexcoords = [];
|
||||
this.blockPalette = [];
|
||||
|
||||
|
||||
this.min = new Vector3( Infinity, Infinity, Infinity);
|
||||
this.max = new Vector3(-Infinity, -Infinity, -Infinity);
|
||||
|
||||
this.voxelsHash = new HashMap(2048);
|
||||
this._voxelsHash = new HashMap(2048);
|
||||
}
|
||||
|
||||
public isVoxelAt(pos: Vector3) {
|
||||
return this.voxelsHash.has(pos);
|
||||
}
|
||||
return this._voxelsHash.has(pos);
|
||||
}
|
||||
|
||||
private _assignBlock(voxelIndex: number, block: BlockInfo) {
|
||||
this.voxels[voxelIndex].block = block.name;
|
||||
@ -82,7 +80,7 @@ export class VoxelManager {
|
||||
|
||||
for (let i = 0; i < this.voxels.length; ++i) {
|
||||
const voxel = this.voxels[i];
|
||||
|
||||
|
||||
const averageColour = getAverageColour(voxel.colours!);
|
||||
|
||||
const ditheringEnabled = AppContext.Get.dithering;
|
||||
@ -96,7 +94,7 @@ export class VoxelManager {
|
||||
}
|
||||
|
||||
meanSquaredError /= this.voxels.length;
|
||||
console.log("Mean Squared Error:", meanSquaredError);
|
||||
console.log('Mean Squared Error:', meanSquaredError);
|
||||
}
|
||||
|
||||
public assignBlankBlocks() {
|
||||
@ -112,13 +110,13 @@ export class VoxelManager {
|
||||
|
||||
public addVoxel(pos: Vector3, block: BlockInfo) {
|
||||
// Is there already a voxel in this position?
|
||||
let voxel = this.voxelsHash.get(pos);
|
||||
if (voxel !== undefined) {
|
||||
let voxel = this._voxelsHash.get(pos);
|
||||
if (voxel !== undefined) {
|
||||
voxel.colours!.push(block.colour);
|
||||
} else {
|
||||
voxel = {position: pos, colours: [block.colour]};
|
||||
this.voxels.push(voxel);
|
||||
this.voxelsHash.add(pos, voxel);
|
||||
this._voxelsHash.add(pos, voxel);
|
||||
}
|
||||
|
||||
this.min = Vector3.min(this.min, pos);
|
||||
@ -129,7 +127,7 @@ export class VoxelManager {
|
||||
if (this._blockMode === MaterialType.Fill) {
|
||||
return this._currentColour;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Could cache dist values
|
||||
const dist01 = Vector3.sub(triangle.v0.position, triangle.v1.position).magnitude();
|
||||
const dist12 = Vector3.sub(triangle.v1.position, triangle.v2.position).magnitude();
|
||||
@ -151,34 +149,33 @@ export class VoxelManager {
|
||||
const uv = {
|
||||
u: triangle.v0.texcoord.u * w0 + triangle.v1.texcoord.u * w1 + triangle.v2.texcoord.u * w2,
|
||||
v: triangle.v0.texcoord.v * w0 + triangle.v1.texcoord.v * w1 + triangle.v2.texcoord.v * w2,
|
||||
}
|
||||
};
|
||||
|
||||
return this._currentTexture.getRGBA(uv);
|
||||
}
|
||||
|
||||
public voxeliseTriangle(triangle: Triangle) {
|
||||
|
||||
const voxelSize = VoxelManager.Get._voxelSize;
|
||||
const voxelSize = VoxelManager.Get.voxelSize;
|
||||
const v0Scaled = Vector3.divScalar(triangle.v0.position, voxelSize);
|
||||
const v1Scaled = Vector3.divScalar(triangle.v1.position, voxelSize);
|
||||
const v2Scaled = Vector3.divScalar(triangle.v2.position, voxelSize);
|
||||
|
||||
const rayList = generateRays(v0Scaled, v1Scaled, v2Scaled);
|
||||
|
||||
rayList.forEach(ray => {
|
||||
rayList.forEach((ray) => {
|
||||
const intersection = rayIntersectTriangle(ray, v0Scaled, v1Scaled, v2Scaled);
|
||||
if (intersection) {
|
||||
let voxelPosition: Vector3;
|
||||
switch (ray.axis) {
|
||||
case Axes.x:
|
||||
voxelPosition = new Vector3(Math.round(intersection.x), intersection.y, intersection.z);
|
||||
break;
|
||||
case Axes.y:
|
||||
voxelPosition = new Vector3(intersection.x, Math.round(intersection.y), intersection.z);
|
||||
break;
|
||||
case Axes.z:
|
||||
voxelPosition = new Vector3(intersection.x, intersection.y, Math.round(intersection.z));
|
||||
break;
|
||||
case Axes.x:
|
||||
voxelPosition = new Vector3(Math.round(intersection.x), intersection.y, intersection.z);
|
||||
break;
|
||||
case Axes.y:
|
||||
voxelPosition = new Vector3(intersection.x, Math.round(intersection.y), intersection.z);
|
||||
break;
|
||||
case Axes.z:
|
||||
voxelPosition = new Vector3(intersection.x, intersection.y, Math.round(intersection.z));
|
||||
break;
|
||||
}
|
||||
|
||||
const voxelColour = this._getVoxelColour(triangle, Vector3.mulScalar(voxelPosition, voxelSize));
|
||||
@ -196,7 +193,7 @@ export class VoxelManager {
|
||||
voxeliseMesh(mesh: Mesh) {
|
||||
this._clearVoxels();
|
||||
|
||||
mesh.materials.forEach(material => {
|
||||
mesh.materials.forEach((material) => {
|
||||
// Setup material
|
||||
if (material.materialData?.type === MaterialType.Texture) {
|
||||
this._blockMode = MaterialType.Texture;
|
||||
@ -206,13 +203,11 @@ export class VoxelManager {
|
||||
this._blockMode = MaterialType.Fill;
|
||||
}
|
||||
// Handle triangles
|
||||
material.faces.forEach(face => {
|
||||
material.faces.forEach((face) => {
|
||||
this.voxeliseTriangle(face);
|
||||
});
|
||||
});
|
||||
|
||||
this.assignBlankBlocks();
|
||||
//this.assignBlocks();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user