forked from mirror/ObjToSchematic
Rewrote parser, again, and misc fixes
This commit is contained in:
parent
39a83f9d0c
commit
78936d82f2
@ -7,7 +7,7 @@
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --fix ./src/**/*.ts && eslint --fix ./src/**/*.ts",
|
||||
"lint": "eslint --fix ./src/**/*.ts && eslint --fix ./tools/**/*.ts",
|
||||
"debug": "tsc && electron ./dist/main.js --enable-logging",
|
||||
"build": "npm run lint && tsc",
|
||||
"start": "npm run build && electron ./dist/main.js --enable-logging",
|
||||
@ -49,11 +49,9 @@
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"expand-vertex-data": "^1.1.2",
|
||||
"jpeg-js": "^0.4.3",
|
||||
"mtltojs": "^0.2.1",
|
||||
"obj-file-parser": "^0.5.3",
|
||||
"pngjs": "^6.0.0",
|
||||
"prismarine-nbt": "^1.6.0",
|
||||
"twgl.js": "^4.19.1",
|
||||
"wavefront-obj-parser": "^2.0.1"
|
||||
"twgl.js": "^4.19.1"
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { Litematic, Schematic } from './schematic';
|
||||
import { Renderer } from './renderer';
|
||||
import { Mesh } from './mesh';
|
||||
import { ObjImporter } from './importers/obj_importer';
|
||||
import { ASSERT, CustomError, CustomWarning, LOG, LOG_ERROR } from './util';
|
||||
import { ASSERT, CustomError, CustomWarning, LOG, LOG_ERROR, LOG_WARN } from './util';
|
||||
|
||||
import { remote } from 'electron';
|
||||
import { VoxelMesh } from './voxel_mesh';
|
||||
@ -131,7 +131,7 @@ export class AppContext {
|
||||
const successMessage = ReturnMessages.get(action)!.onSuccess;
|
||||
if (this._warnings.length !== 0) {
|
||||
const allWarnings = this._warnings.join('<br>');
|
||||
UI.Get.layoutDull[groupName].output.setMessage(successMessage + ', with warnings:' + '<br><b>' + allWarnings + '</b>', ActionReturnType.Warning);
|
||||
UI.Get.layoutDull[groupName].output.setMessage(successMessage + `, with ${this._warnings.length} warning(s):` + '<br><b>' + allWarnings + '</b>', ActionReturnType.Warning);
|
||||
} else {
|
||||
UI.Get.layoutDull[groupName].output.setMessage(successMessage, ActionReturnType.Success);
|
||||
}
|
||||
@ -197,4 +197,9 @@ export class AppContext {
|
||||
public getLoadedMesh() {
|
||||
return this._loadedMesh;
|
||||
}
|
||||
|
||||
public addWarning(warning: string) {
|
||||
LOG_WARN(warning);
|
||||
this._warnings.push(warning);
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,6 @@ export class GeometryTemplates {
|
||||
static getTriangleBufferData(triangle: UVTriangle): VoxelData {
|
||||
const n = triangle.getNormal();
|
||||
|
||||
const uv0u = triangle.uv0.u;
|
||||
if (isNaN(uv0u)) {
|
||||
throw Error('oh no');
|
||||
}
|
||||
|
||||
return {
|
||||
custom: {
|
||||
position: [
|
||||
|
@ -1,120 +1,292 @@
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
const OBJFile = require('obj-file-parser');
|
||||
const mtlParser = require('mtltojs');
|
||||
|
||||
import { IImporter } from '../importer';
|
||||
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
|
||||
import { Vector3 } from '../vector';
|
||||
import { UV, ASSERT, RGB, LOG_WARN } from '../util';
|
||||
import { UV, ASSERT, RGB, CustomError, LOG } from '../util';
|
||||
import { UI } from '../ui/layout';
|
||||
import { checkFractional, checkNaN } from '../math';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { AppContext } from '../app_context';
|
||||
|
||||
export class ObjImporter extends IImporter {
|
||||
private _vertices!: Vector3[];
|
||||
private _uvs!: UV[];
|
||||
private _tris!: Tri[];
|
||||
private _materials!: {[key: string]: (SolidMaterial | TexturedMaterial)};
|
||||
private _mtlLibs!: string[];
|
||||
private _vertices: Vector3[] = [];
|
||||
private _uvs: UV[] = [];
|
||||
private _tris: Tri[] = [];
|
||||
private _materials: {[key: string]: (SolidMaterial | TexturedMaterial)} = {};
|
||||
|
||||
private _mtlLibs: string[] = [];
|
||||
private _currentMaterialName: string = '';
|
||||
private _objPath?: path.ParsedPath;
|
||||
private _objParsers = [
|
||||
{
|
||||
regex: /mtllib (?<path>.*\.mtl)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
this._mtlLibs.push(match.path);
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /usemtl (?<name>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
this._currentMaterialName = match.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /v (?<x>.*) (?<y>.*) (?<z>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const x = parseFloat(match.x);
|
||||
const y = parseFloat(match.y);
|
||||
const z = parseFloat(match.z);
|
||||
checkNaN(x, y, z);
|
||||
this._vertices.push(new Vector3(x, y, z));
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /vt (?<u>.*) (?<v>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const u = parseFloat(match.u);
|
||||
const v = parseFloat(match.v);
|
||||
checkNaN(u, v);
|
||||
this._uvs.push(new UV(u, v));
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /f (?<ix>.*)\/(?<iuvx>.*)\/.* (?<iy>.*)\/(?<iuvy>.*)\/.* (?<iz>.*)\/(?<iuvz>.*)\/.* (?<iw>.*)\/(?<iuvw>.*)\//,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const iX = parseInt(match.ix) - 1;
|
||||
const iY = parseInt(match.iy) - 1;
|
||||
const iZ = parseInt(match.iz) - 1;
|
||||
const iW = parseInt(match.iw) - 1;
|
||||
const iUVx = parseInt(match.iuvx) - 1;
|
||||
const iUVy = parseInt(match.iuvy) - 1;
|
||||
const iUVz = parseInt(match.iuvz) - 1;
|
||||
const iUVw = parseInt(match.iuvw) - 1;
|
||||
checkNaN(iX, iY, iZ, iW);
|
||||
ASSERT(this._currentMaterialName);
|
||||
this._tris.push({
|
||||
iX: iW,
|
||||
iY: iY,
|
||||
iZ: iX,
|
||||
iXUV: iUVw,
|
||||
iYUV: iUVy,
|
||||
iZUV: iUVx,
|
||||
material: this._currentMaterialName,
|
||||
});
|
||||
this._tris.push({
|
||||
iX: iW,
|
||||
iY: iZ,
|
||||
iZ: iY,
|
||||
iXUV: iUVw,
|
||||
iYUV: iUVz,
|
||||
iZUV: iUVy,
|
||||
material: this._currentMaterialName,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /f (?<ix>.*)\/(?<iuvx>.*)\/.* (?<iy>.*)\/(?<iuvy>.*)\/.* (?<iz>.*)\/(?<iuvz>.*)\//,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const iX = parseInt(match.ix) - 1;
|
||||
const iY = parseInt(match.iy) - 1;
|
||||
const iZ = parseInt(match.iz) - 1;
|
||||
const iUVx = parseInt(match.iuvx) - 1;
|
||||
const iUVy = parseInt(match.iuvy) - 1;
|
||||
const iUVz = parseInt(match.iuvz) - 1;
|
||||
checkNaN(iX, iY, iZ);
|
||||
ASSERT(this._currentMaterialName);
|
||||
this._tris.push({
|
||||
iX: iX,
|
||||
iY: iY,
|
||||
iZ: iZ,
|
||||
iXUV: iUVx,
|
||||
iYUV: iUVy,
|
||||
iZUV: iUVz,
|
||||
material: this._currentMaterialName,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /f (?<ix>.*) (?<iy>.*) (?<iz>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const iX = parseInt(match.ix) - 1;
|
||||
const iY = parseInt(match.iy) - 1;
|
||||
const iZ = parseInt(match.iz) - 1;
|
||||
checkNaN(iX, iY, iZ);
|
||||
ASSERT(this._currentMaterialName);
|
||||
this._tris.push({
|
||||
iX: iX,
|
||||
iY: iY,
|
||||
iZ: iZ,
|
||||
iXUV: iX,
|
||||
iYUV: iY,
|
||||
iZUV: iZ,
|
||||
material: this._currentMaterialName,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
private _currentColour: RGB = RGB.black;
|
||||
private _currentTexture: string = '';
|
||||
private _materialReady: boolean = false;
|
||||
private _mtlParsers = [
|
||||
{
|
||||
regex: /newmtl (?<name>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
this._addCurrentMaterial();
|
||||
this._currentMaterialName = match.name;
|
||||
this._currentTexture = '';
|
||||
this._materialReady = false;
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /Kd (?<r>.*) (?<g>.*) (?<b>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const r = parseFloat(match.r);
|
||||
const g = parseFloat(match.g);
|
||||
const b = parseFloat(match.b);
|
||||
checkNaN(r, g, b);
|
||||
checkFractional(r, g, b);
|
||||
this._currentColour = new RGB(r, g, b);
|
||||
this._materialReady = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /map_Kd (?<path>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
let mtlPath = match.path;
|
||||
if (!path.isAbsolute(mtlPath)) {
|
||||
ASSERT(this._objPath);
|
||||
mtlPath = path.join(this._objPath.dir, mtlPath);
|
||||
}
|
||||
this._currentTexture = mtlPath;
|
||||
this._materialReady = true;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
override createMesh(): Mesh {
|
||||
const filePath = UI.Get.layout.import.elements.input.getCachedValue();
|
||||
ASSERT(path.isAbsolute(filePath));
|
||||
|
||||
this._objPath = path.parse(filePath);
|
||||
|
||||
this._parseOBJ(filePath);
|
||||
|
||||
ASSERT(this._mtlLibs.length > 0);
|
||||
if (this._mtlLibs.length > 1) {
|
||||
LOG_WARN('Multiple mtl libs found, only the first will be used');
|
||||
if (this._mtlLibs.length === 0) {
|
||||
AppContext.Get.addWarning('Could not find associated .mtl file');
|
||||
}
|
||||
let mtlPath = this._mtlLibs[0];
|
||||
if (!path.isAbsolute(mtlPath)) {
|
||||
const objPath = path.parse(filePath);
|
||||
mtlPath = path.join(objPath.dir, mtlPath);
|
||||
for (let i = 0; i < this._mtlLibs.length; ++i) {
|
||||
const mtlLib = this._mtlLibs[i];
|
||||
if (!path.isAbsolute(mtlLib)) {
|
||||
this._mtlLibs[i] = path.join(this._objPath.dir, mtlLib);
|
||||
}
|
||||
ASSERT(path.isAbsolute(this._mtlLibs[i]));
|
||||
}
|
||||
|
||||
ASSERT(path.isAbsolute(mtlPath));
|
||||
this._parseMTL(mtlPath);
|
||||
this._parseMTL();
|
||||
|
||||
LOG(this);
|
||||
return new Mesh(this._vertices, this._uvs, this._tris, this._materials);
|
||||
}
|
||||
|
||||
private _parseOBJ(path: string) {
|
||||
const fileContents = fs.readFileSync(path, 'utf-8');
|
||||
const objFile = new OBJFile(fileContents);
|
||||
const output = objFile.parse();
|
||||
if (!fs.existsSync(path)) {
|
||||
throw new CustomError(`Could not find ${path}`);
|
||||
}
|
||||
const fileContents = fs.readFileSync(path, 'utf8');
|
||||
if (fileContents.includes('<27>')) {
|
||||
throw new CustomError(`Unrecognised character found, please encode <b>${path}</b> using UTF-8`);
|
||||
}
|
||||
|
||||
this._mtlLibs = output.materialLibraries;
|
||||
fileContents.replace('\r', ''); // Convert Windows carriage return
|
||||
const fileLines = fileContents.split('\n');
|
||||
|
||||
for (const modelName in output.models) {
|
||||
const model = output.models[modelName];
|
||||
// Fill vertices
|
||||
this._vertices = [];
|
||||
for (const vertex of model.vertices) {
|
||||
this._vertices.push(new Vector3(vertex.x, vertex.y, vertex.z));
|
||||
}
|
||||
// Fill UVs
|
||||
this._uvs = [];
|
||||
for (const uv of model.textureCoords) {
|
||||
this._uvs.push({ u: uv.u, v: uv.v });
|
||||
}
|
||||
// Fill tris
|
||||
this._tris = [];
|
||||
for (const tri of model.faces) {
|
||||
if (tri.vertices.length === 3) {
|
||||
const iX = tri.vertices[0].vertexIndex - 1;
|
||||
const iXUV = tri.vertices[0].textureCoordsIndex - 1;
|
||||
const iY = tri.vertices[1].vertexIndex - 1;
|
||||
const iYUV = tri.vertices[1].textureCoordsIndex - 1;
|
||||
const iZ = tri.vertices[2].vertexIndex - 1;
|
||||
const iZUV = tri.vertices[2].textureCoordsIndex - 1;
|
||||
const material = tri.material;
|
||||
this._tris.push({
|
||||
iX: iX, iY: iY, iZ: iZ,
|
||||
iXUV: iXUV, iYUV: iYUV, iZUV: iZUV,
|
||||
material: material,
|
||||
});
|
||||
} else if (tri.vertices.length === 4) {
|
||||
const iX = tri.vertices[0].vertexIndex - 1;
|
||||
const iXUV = tri.vertices[0].textureCoordsIndex - 1;
|
||||
const iY = tri.vertices[1].vertexIndex - 1;
|
||||
const iYUV = tri.vertices[1].textureCoordsIndex - 1;
|
||||
const iZ = tri.vertices[2].vertexIndex - 1;
|
||||
const iZUV = tri.vertices[2].textureCoordsIndex - 1;
|
||||
const iW = tri.vertices[3].vertexIndex - 1;
|
||||
const iWUV = tri.vertices[3].textureCoordsIndex - 1;
|
||||
const material = tri.material;
|
||||
this._tris.push({
|
||||
iX: iW, iY: iY, iZ: iX,
|
||||
iXUV: iWUV, iYUV: iYUV, iZUV: iXUV,
|
||||
material: material,
|
||||
});
|
||||
this._tris.push({
|
||||
iX: iW, iY: iZ, iZ: iY,
|
||||
iXUV: iWUV, iYUV: iZUV, iZUV: iYUV,
|
||||
material: material,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const line of fileLines) {
|
||||
this._parseOBJLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseMTL(mtlPath: string) {
|
||||
const output = mtlParser.parseSync(mtlPath);
|
||||
const materials = output.data.data.material;
|
||||
private _parseOBJLine(line: string) {
|
||||
const essentialTokens = ['mtllib ', 'uselib ', 'v ', 'vt ', 'f '];
|
||||
|
||||
this._materials = {};
|
||||
for (const material of materials) {
|
||||
if (material?.texture_map?.diffuse) {
|
||||
let texPath = material.texture_map.diffuse.file;
|
||||
if (!path.isAbsolute(texPath)) {
|
||||
const parsedPath = path.parse(mtlPath);
|
||||
texPath = path.join(parsedPath.dir, texPath);
|
||||
for (const parser of this._objParsers) {
|
||||
const match = parser.regex.exec(line);
|
||||
if (match && match.groups) {
|
||||
try {
|
||||
parser.delegate(match.groups);
|
||||
} catch (error) {
|
||||
if (error instanceof CustomError) {
|
||||
throw new CustomError(`Failed attempt to parse '${line}', because '${error.message}'`);
|
||||
}
|
||||
}
|
||||
this._materials[material.name] = { type: MaterialType.textured, path: texPath };
|
||||
} else if (material?.diffuse) {
|
||||
const rgb = material.diffuse.vals;
|
||||
this._materials[material.name] = { type: MaterialType.solid, colour: RGB.fromArray(rgb) };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const beginsWithEssentialToken = essentialTokens.some((token) => {
|
||||
return line.startsWith(token);
|
||||
});
|
||||
if (beginsWithEssentialToken) {
|
||||
throw new CustomError(`Failed to parse essential token for ${line}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseMTL() {
|
||||
for (const mtlLib of this._mtlLibs) {
|
||||
if (!fs.existsSync(mtlLib)) {
|
||||
throw new CustomError(`Could not find ${mtlLib}`);
|
||||
}
|
||||
const fileContents = fs.readFileSync(mtlLib, 'utf8');
|
||||
|
||||
fileContents.replace('\r', ''); // Convert Windows carriage return
|
||||
const fileLines = fileContents.split('\n');
|
||||
|
||||
for (const line of fileLines) {
|
||||
this._parseMTLLine(line);
|
||||
}
|
||||
|
||||
this._addCurrentMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
private _parseMTLLine(line: string) {
|
||||
const essentialTokens = ['newmtl ', 'Kd ', 'map_Kd '];
|
||||
|
||||
for (const parser of this._mtlParsers) {
|
||||
const match = parser.regex.exec(line);
|
||||
if (match && match.groups) {
|
||||
try {
|
||||
parser.delegate(match.groups);
|
||||
} catch (error) {
|
||||
if (error instanceof CustomError) {
|
||||
throw new CustomError(`Failed attempt to parse '${line}', because '${error.message}'`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const beginsWithEssentialToken = essentialTokens.some((token) => {
|
||||
return line.startsWith(token);
|
||||
});
|
||||
if (beginsWithEssentialToken) {
|
||||
throw new CustomError(`Failed to parse essential token for ${line}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _addCurrentMaterial() {
|
||||
if (this._materialReady && this._currentMaterialName !== '') {
|
||||
if (this._currentTexture !== '') {
|
||||
this._materials[this._currentMaterialName] = {
|
||||
type: MaterialType.textured,
|
||||
path: this._currentTexture,
|
||||
};
|
||||
} else {
|
||||
ASSERT(false);
|
||||
this._materials[this._currentMaterialName] = {
|
||||
type: MaterialType.solid,
|
||||
colour: this._currentColour,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
src/math.ts
26
src/math.ts
@ -1,3 +1,4 @@
|
||||
import { CustomError, LOG_ERROR } from './util';
|
||||
import { Vector3 } from './vector';
|
||||
|
||||
|
||||
@ -38,4 +39,29 @@ export const wayThrough = (value: number, min: number, max: number) => {
|
||||
return (value - min) / (max - min);
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws is any number in args is NaN
|
||||
*/
|
||||
export const checkNaN = (...args: number[]) => {
|
||||
const existsNaN = args.some((arg) => {
|
||||
return isNaN(arg);
|
||||
});
|
||||
if (existsNaN) {
|
||||
LOG_ERROR(args);
|
||||
throw new CustomError('Found NaN');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws if any number in not within [0, 1]
|
||||
*/
|
||||
export const checkFractional = (...args: number[]) => {
|
||||
const existsOutside = args.some((arg) => {
|
||||
return arg > 1.0 || arg < 0.0;
|
||||
});
|
||||
if (existsOutside) {
|
||||
throw new CustomError('Found value outside of [0, 1]');
|
||||
}
|
||||
};
|
||||
|
||||
export const degreesToRadians = Math.PI / 180;
|
||||
|
83
src/mesh.ts
83
src/mesh.ts
@ -1,7 +1,11 @@
|
||||
import { Vector3 } from './vector';
|
||||
import { UV, Bounds, LOG } from './util';
|
||||
import { UV, Bounds, LOG, ASSERT, CustomError, LOG_WARN } from './util';
|
||||
import { Triangle, UVTriangle } from './triangle';
|
||||
import { RGB } from './util';
|
||||
import { AppContext } from './app_context';
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export interface Tri {
|
||||
iX: number;
|
||||
@ -36,8 +40,13 @@ export class Mesh {
|
||||
this.tris = tris;
|
||||
this.materials = materials;
|
||||
|
||||
this._checkMesh();
|
||||
this._checkMaterials();
|
||||
|
||||
this._centreMesh();
|
||||
this._scaleMesh();
|
||||
|
||||
LOG('Loaded mesh', this);
|
||||
}
|
||||
|
||||
public getBounds() {
|
||||
@ -48,6 +57,57 @@ export class Mesh {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private _checkMesh() {
|
||||
// Check UVs are inside [0, 1]
|
||||
for (const uv of this.uvs) {
|
||||
if (uv.u < 0.0 || uv.u > 1.0) {
|
||||
uv.u = Math.abs(uv.u % 1);
|
||||
}
|
||||
if (uv.v < 0.0 || uv.v > 1.0) {
|
||||
uv.v = Math.abs(uv.v % 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _checkMaterials() {
|
||||
// Check used materials exist
|
||||
let wasRemapped = false;
|
||||
let debugName = (Math.random() + 1).toString(36).substring(7);
|
||||
while (debugName in this.materials) {
|
||||
debugName = (Math.random() + 1).toString(36).substring(7);
|
||||
}
|
||||
|
||||
for (const tri of this.tris) {
|
||||
if (!(tri.material in this.materials)) {
|
||||
wasRemapped = true;
|
||||
tri.material = debugName;
|
||||
}
|
||||
}
|
||||
if (wasRemapped) {
|
||||
AppContext.Get.addWarning('Some materials were not loaded correctly');
|
||||
this.materials[debugName] = {
|
||||
type: MaterialType.solid,
|
||||
colour: RGB.white,
|
||||
};
|
||||
}
|
||||
|
||||
// Check texture paths are absolute and exist
|
||||
for (const materialName in this.materials) {
|
||||
const material = this.materials[materialName];
|
||||
if (material.type === MaterialType.textured) {
|
||||
ASSERT(path.isAbsolute(material.path), 'Material texture path not absolute');
|
||||
if (!fs.existsSync(material.path)) {
|
||||
AppContext.Get.addWarning(`Could not find ${material.path}`);
|
||||
LOG_WARN(`Could not find ${material.path} for material ${materialName}, changing to solid-white material`);
|
||||
this.materials[materialName] = {
|
||||
type: MaterialType.solid,
|
||||
colour: RGB.white,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _centreMesh() {
|
||||
const centre = new Vector3(0, 0, 0);
|
||||
let totalWeight = 0.0;
|
||||
@ -63,6 +123,11 @@ export class Mesh {
|
||||
});
|
||||
centre.divScalar(totalWeight);
|
||||
|
||||
if (!centre.isNumber()) {
|
||||
throw new CustomError('Could not find centre of mesh');
|
||||
}
|
||||
LOG('Centre', centre);
|
||||
|
||||
// Translate each triangle
|
||||
this.vertices.forEach((vertex) => {
|
||||
vertex.sub(centre);
|
||||
@ -74,9 +139,13 @@ export class Mesh {
|
||||
const size = Vector3.sub(bounds.max, bounds.min);
|
||||
const scaleFactor = Mesh.desiredHeight / size.y;
|
||||
|
||||
this.vertices.forEach((vertex) => {
|
||||
vertex.mulScalar(scaleFactor);
|
||||
});
|
||||
if (isNaN(scaleFactor) || !isFinite(scaleFactor)) {
|
||||
throw new CustomError('<b>Could not scale mesh correctly</b>: Mesh is likely 2D, rotate it so that it has a non-zero height');
|
||||
} else {
|
||||
this.vertices.forEach((vertex) => {
|
||||
vertex.mulScalar(scaleFactor);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getVertices(triIndex: number) {
|
||||
@ -103,9 +172,9 @@ export class Mesh {
|
||||
this.vertices[tri.iX],
|
||||
this.vertices[tri.iY],
|
||||
this.vertices[tri.iZ],
|
||||
this.uvs[tri.iXUV],
|
||||
this.uvs[tri.iYUV],
|
||||
this.uvs[tri.iZUV],
|
||||
this.uvs[tri.iXUV] || 0.0,
|
||||
this.uvs[tri.iYUV] || 0.0,
|
||||
this.uvs[tri.iZUV] || 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,8 @@ export class Renderer {
|
||||
|
||||
public useMesh(mesh: Mesh) {
|
||||
LOG('Using mesh');
|
||||
this._materialBuffers = [];
|
||||
|
||||
for (const materialName in mesh.materials) {
|
||||
const materialBuffer = new RenderBuffer([
|
||||
{ name: 'position', numComponents: 3 },
|
||||
@ -92,14 +94,17 @@ export class Renderer {
|
||||
{ name: 'normal', numComponents: 3 },
|
||||
]);
|
||||
|
||||
for (let triIndex = 0; triIndex < mesh.tris.length; ++triIndex) {
|
||||
const uvTri = mesh.getUVTriangle(triIndex);
|
||||
const triGeom = GeometryTemplates.getTriangleBufferData(uvTri);
|
||||
materialBuffer.add(triGeom);
|
||||
}
|
||||
mesh.tris.forEach((tri, triIndex) => {
|
||||
if (tri.material === materialName) {
|
||||
if (tri.material === materialName) {
|
||||
const uvTri = mesh.getUVTriangle(triIndex);
|
||||
const triGeom = GeometryTemplates.getTriangleBufferData(uvTri);
|
||||
materialBuffer.add(triGeom);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const material = mesh.materials[materialName];
|
||||
this._materialBuffers = [];
|
||||
if (material.type === MaterialType.solid) {
|
||||
this._materialBuffers.push({
|
||||
buffer: materialBuffer,
|
||||
|
@ -77,7 +77,7 @@ export class UI {
|
||||
]),
|
||||
'colourSpace': new ComboBoxElement('Colour space', [
|
||||
{ id: 'lab', displayText: 'LAB (recommended)' },
|
||||
{ id: 'rgb', displayText: 'RGB' },
|
||||
{ id: 'rgb', displayText: 'RGB (faster)' },
|
||||
]),
|
||||
},
|
||||
elementsOrder: ['textureAtlas', 'blockPalette', 'dithering', 'colourSpace'],
|
||||
|
@ -220,6 +220,10 @@ export class Vector3 extends Hashable {
|
||||
return new Vector3(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
public isNumber() {
|
||||
return !isNaN(this.x) && !isNaN(this.y) && !isNaN(this.z);
|
||||
}
|
||||
|
||||
// Begin IHashable interface
|
||||
override hash() {
|
||||
const p0 = 73856093;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { LOG, RGB, UV } from '../src/util';
|
||||
import { RGB, UV } from '../src/util';
|
||||
import { log, logBreak, LogStyle } from './logging';
|
||||
import { isDirSetup, ASSERT, getAverageColour, getPermission } from './misc';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
/* eslint-disable */
|
||||
export enum LogStyle {
|
||||
None = 'None',
|
||||
Info = 'Info',
|
||||
@ -7,6 +8,7 @@ export enum LogStyle {
|
||||
Failure = 'Failure',
|
||||
Success = 'Success'
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
const LogStyleDetails: {[style: string]: {style: chalk.Chalk, prefix: string}} = {};
|
||||
LogStyleDetails[LogStyle.Info] = {style: chalk.blue, prefix: chalk.blue.inverse('INFO')};
|
||||
|
Loading…
Reference in New Issue
Block a user