Compare commits

...

2 Commits
main ... fbx

Author SHA1 Message Date
Lucas Dower
53456e957e Added basic .fbx parsing 2022-04-24 03:19:02 +01:00
Lucas Dower
1a7c33b9b1 Updated file importing to support non-obj files 2022-04-23 20:24:21 +01:00
10 changed files with 179 additions and 897 deletions

919
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -54,6 +54,7 @@
"bvh": "^0.5.0",
"bvh-tree": "^1.0.1",
"color-convert": "^2.0.1",
"fbx-parser": "^2.1.3",
"jpeg-js": "^0.4.3",
"pngjs": "^6.0.0",
"prismarine-nbt": "^1.6.0",

View File

@ -3,7 +3,6 @@ import { Schematic } from './exporters/schematic_exporter';
import { Litematic } from './exporters/litematic_exporter';
import { Renderer } from './renderer';
import { Mesh } from './mesh';
import { ObjImporter } from './importers/obj_importer';
import { ASSERT, ColourSpace, AppError, LOG, LOG_ERROR, LOG_WARN, TIME_START, TIME_END } from './util';
import { remote } from 'electron';
@ -18,6 +17,9 @@ import { StatusHandler } from './status';
import { UIMessageBuilder } from './ui/misc';
import { OutputStyle } from './ui/elements/output';
import path from 'path';
import { MeshImporter } from './mesh_importer';
/* eslint-disable */
export enum EAction {
Import = 0,
@ -137,9 +139,7 @@ export class AppContext {
const uiElements = this._ui.layout.import.elements;
const filePath = uiElements.input.getCachedValue();
const importer = new ObjImporter();
importer.parseFile(filePath);
this._loadedMesh = importer.toMesh();
this._loadedMesh = MeshImporter.import(filePath);
this._loadedMesh.processMesh();
Renderer.Get.useMesh(this._loadedMesh);
}

View File

@ -1,6 +0,0 @@
import { Mesh } from './mesh';
export abstract class IImporter {
abstract parseFile(filePath: string): void;
abstract toMesh(): Mesh;
}

View File

@ -0,0 +1,11 @@
import { Mesh } from '../mesh';
export abstract class IImporter {
public abstract parseFile(filePath: string): void;
public abstract toMesh(): Mesh;
public supports(fileExtension: string) {
return this.getSupportedFileExtensions().includes(fileExtension);
}
protected abstract getSupportedFileExtensions(): string[];
}

View File

@ -0,0 +1,91 @@
import { IImporter } from './base_importer';
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
import { Vector3 } from '../vector';
import { UV, RGB, LOG, ASSERT } from '../util';
import fs from 'fs';
import * as FBXParser from 'fbx-parser';
export class FbxImporter extends IImporter {
private _vertices: Vector3[] = [];
private _normals: Vector3[] = [];
private _uvs: UV[] = [];
private _tris: Tri[] = [];
private _materials: {[key: string]: (SolidMaterial | TexturedMaterial)} = {
'DEFAULT_UNASSIGNED': { type: MaterialType.solid, colour: RGB.white },
};
public override parseFile(filePath: string) {
let fbx: FBXParser.FBXData;
try {
fbx = FBXParser.parseBinary(fs.readFileSync(filePath));
} catch (e) {
fbx = FBXParser.parseText(fs.readFileSync(filePath, 'utf-8'));
}
const root = new FBXParser.FBXReader(fbx);
// Parse vertex data
const _vertices = root.node('Objects')?.node('Geometry')?.node('Vertices')?.prop(0, 'number[]');
ASSERT(_vertices, 'Could not find vertex data node');
ASSERT(_vertices.length % 3 === 0, 'Expected vertices array to be a multiple of 3');
const numVertices = _vertices.length / 3;
this._vertices = Array<Vector3>(numVertices);
for (let i = 0; i < numVertices; ++i) {
this._vertices[i] = new Vector3(
_vertices[i * 3 + 0],
_vertices[i * 3 + 1],
_vertices[i * 3 + 2],
);
}
// LOG('vertices', this._vertices);
// Parse face data
const _faces = root.node('Objects')?.node('Geometry')?.node('PolygonVertexIndex')?.prop(0, 'number[]');
ASSERT(_faces, 'Could not find face data node');
let base = [];
for (let i = 0; i < _faces.length; ++i) {
base.push(_faces[i]);
if (_faces[i] < 0) {
this._addFaceData(base);
base = [];
}
}
// LOG('tris', this._tris);
LOG(root);
}
private _addFaceData(points: number[]) {
// Make all indices positive
for (let i = 0; i < points.length; ++i) {
if (points[i] < 0) {
points[i] = (-points[i]) - 1;
}
}
const pointBase = points[0];
for (let i = 1; i < points.length - 1; ++i) {
const pointA = points[i];
const pointB = points[i+1];
const tri: Tri = {
positionIndices: {
x: pointBase,
y: pointA,
z: pointB,
},
material: 'None',
};
this._tris.push(tri);
}
}
public override toMesh(): Mesh {
return new Mesh(this._vertices, this._normals, this._uvs, this._tris, this._materials);
}
public override getSupportedFileExtensions(): string[] {
return ['.fbx'];
}
}

View File

@ -1,4 +1,4 @@
import { IImporter } from '../importer';
import { IImporter } from './base_importer';
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
import { Vector3 } from '../vector';
import { UV, ASSERT, RGB, AppError, REGEX_NUMBER, RegExpBuilder, REGEX_NZ_ANY, LOG_ERROR } from '../util';
@ -237,7 +237,7 @@ export class ObjImporter extends IImporter {
},
];
override parseFile(filePath: string) {
public parseFile(filePath: string) {
ASSERT(path.isAbsolute(filePath), 'path not absolute');
this._objPath = path.parse(filePath);
@ -363,4 +363,8 @@ export class ObjImporter extends IImporter {
}
}
}
public override getSupportedFileExtensions(): string[] {
return ['.obj'];
}
}

22
src/mesh_importer.ts Normal file
View File

@ -0,0 +1,22 @@
import { ObjImporter } from './importers/obj_importer';
import { FbxImporter } from './importers/fbx_importer';
import { AppError, ASSERT } from './util';
import path from 'path';
export class MeshImporter {
public static import(filepath: string) {
ASSERT(path.isAbsolute(filepath));
const parsedPath = path.parse(filepath);
const importers = [new ObjImporter(), new FbxImporter()];
for (const importer of importers) {
if (importer.supports(parsedPath.ext.toLowerCase())) {
importer.parseFile(filepath);
return importer.toMesh();
}
}
throw new AppError(`Could not load file-type '${parsedPath.ext.toLowerCase()}' when loading ${parsedPath.base}`);
}
}

View File

@ -5,12 +5,12 @@ import { remote } from 'electron';
import * as path from 'path';
export class FileInputElement extends LabelledElement<string> {
private _fileExtension: string;
private _fileExtensions: string[];
private _loadedFilePath: string;
public constructor(label: string, fileExtension: string) {
public constructor(label: string, fileExtensions: string[]) {
super(label);
this._fileExtension = fileExtension;
this._fileExtensions = fileExtensions;
this._loadedFilePath = '';
}
@ -36,7 +36,7 @@ export class FileInputElement extends LabelledElement<string> {
buttonLabel: 'Load',
filters: [{
name: 'Waveform obj file',
extensions: [`${this._fileExtension}`],
extensions: this._fileExtensions,
}],
});
if (files && files.length === 1) {

View File

@ -34,7 +34,7 @@ export class UI {
'import': {
label: 'Import',
elements: {
'input': new FileInputElement('Wavefront .obj file', 'obj'),
'input': new FileInputElement('Model file', ['obj', 'fbx']),
},
elementsOrder: ['input'],
submitButton: new ButtonElement('Load mesh', () => {