Poly-face support, ObjImporter tests, parse normal data

This commit is contained in:
Lucas Dower 2022-03-19 00:36:32 +00:00
parent 7b87bfbe61
commit f4ababcd33
5 changed files with 363 additions and 109 deletions

View File

@ -145,7 +145,10 @@ export class AppContext {
const uiElements = this._ui.layout.import.elements;
const filePath = uiElements.input.getCachedValue();
this._loadedMesh = new ObjImporter().createMesh(filePath);
const importer = new ObjImporter();
importer.parseFile(filePath);
this._loadedMesh = importer.toMesh();
this._loadedMesh.processMesh();
Renderer.Get.useMesh(this._loadedMesh);
}

View File

@ -2,5 +2,6 @@ import { Mesh } from './mesh';
import { Warnable } from './util';
export abstract class IImporter extends Warnable {
abstract createMesh(filePath: string): Mesh;
abstract parseFile(filePath: string): void;
abstract toMesh(): Mesh;
}

View File

@ -1,7 +1,7 @@
import { IImporter } from '../importer';
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
import { Vector3 } from '../vector';
import { UV, ASSERT, RGB, CustomError, LOG, REGEX_NUMBER, RegExpBuilder, REGEX_NZ_ANY, LOG_ERROR } from '../util';
import { UV, ASSERT, RGB, CustomError, REGEX_NUMBER, RegExpBuilder, REGEX_NZ_ANY, LOG_ERROR } from '../util';
import { checkFractional, checkNaN } from '../math';
import fs from 'fs';
@ -9,6 +9,7 @@ import path from 'path';
export class ObjImporter extends IImporter {
private _vertices: Vector3[] = [];
private _normals: Vector3[] = [];
private _uvs: UV[] = [];
private _tris: Tri[] = [];
@ -22,14 +23,14 @@ export class ObjImporter extends IImporter {
private _objParsers = [
{
// e.g. 'mtllib my_file.mtl'
regex: new RegExpBuilder().add(/mtllib/).add(/ /).add(REGEX_NZ_ANY, 'path').toRegExp(),
regex: new RegExpBuilder().add(/^mtllib/).add(/ /).add(REGEX_NZ_ANY, 'path').toRegExp(),
delegate: (match: { [key: string]: string }) => {
this._mtlLibs.push(match.path.trim());
},
},
{
// e.g. 'usemtl my_material'
regex: new RegExpBuilder().add(/usemtl/).add(/ /).add(REGEX_NZ_ANY, 'name').toRegExp(),
regex: new RegExpBuilder().add(/^usemtl/).add(/ /).add(REGEX_NZ_ANY, 'name').toRegExp(),
delegate: (match: { [key: string]: string }) => {
this._currentMaterialName = match.name.trim();
ASSERT(this._currentMaterialName, 'invalid material name');
@ -38,7 +39,7 @@ export class ObjImporter extends IImporter {
{
// e.g. 'v 0.123 0.456 0.789'
regex: new RegExpBuilder()
.add(/v/)
.add(/^v/)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'x')
.addNonzeroWhitespace()
@ -54,10 +55,29 @@ export class ObjImporter extends IImporter {
this._vertices.push(new Vector3(x, y, z));
},
},
{
// e.g. 'vn 0.123 0.456 0.789'
regex: new RegExpBuilder()
.add(/^vn/)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'x')
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'y')
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'z')
.toRegExp(),
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._normals.push(new Vector3(x, y, z));
},
},
{
// e.g. 'vt 0.123 0.456'
regex: new RegExpBuilder()
.add(/vt/)
.add(/^vt/)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'u')
.addNonzeroWhitespace()
@ -71,78 +91,96 @@ export class ObjImporter extends IImporter {
},
},
{
// e.g. 'f 1/2/3 4/5/6 7/8/9 10/11/12' or 'f 1/2 3/4 5/6 7/8'
// e.g. 'f 1/2/3 ...' or 'f 1/2 ...' or 'f 1 ...'
regex: new RegExpBuilder()
.add(/f/)
.add(/^f/)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'xIndex').addMany(['/'], true).add(REGEX_NUMBER, 'xtIndex', true).addMany(['/', REGEX_NUMBER], true)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'yIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ytIndex', true).addMany(['/', REGEX_NUMBER], true)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'zIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ztIndex', true).addMany(['/', REGEX_NUMBER], true)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'wIndex').addMany(['/'], true).add(REGEX_NUMBER, 'wtIndex', true).addMany(['/', REGEX_NUMBER], true)
.add(/.*/, 'line')
.toRegExp(),
delegate: (match: { [key: string]: string }) => {
const iX = parseInt(match.xIndex) - 1;
const iY = parseInt(match.yIndex) - 1;
const iZ = parseInt(match.zIndex) - 1;
const iW = parseInt(match.wIndex) - 1;
const iUVx = parseInt(match.xtIndex) - 1;
const iUVy = parseInt(match.ytIndex) - 1;
const iUVz = parseInt(match.ztIndex) - 1;
const iUVw = parseInt(match.wtIndex) - 1;
checkNaN(iX, iY, iZ, iW);
ASSERT(this._currentMaterialName, 'unassigned material');
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,
});
},
},
{
// e.g. f 1/2/3 4/5/6 7/8/9 or 1/2 3/4 5/6
regex: new RegExpBuilder()
.add(/f/)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'xIndex').addMany(['/'], true).add(REGEX_NUMBER, 'xtIndex', true).addMany(['/', REGEX_NUMBER], true)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'yIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ytIndex', true).addMany(['/', REGEX_NUMBER], true)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'zIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ztIndex', true).addMany(['/', REGEX_NUMBER], true)
.toRegExp(),
delegate: (match: { [key: string]: string }) => {
const iX = parseInt(match.xIndex) - 1;
const iY = parseInt(match.yIndex) - 1;
const iZ = parseInt(match.zIndex) - 1;
const iUVx = parseInt(match.xtIndex) - 1;
const iUVy = parseInt(match.ytIndex) - 1;
const iUVz = parseInt(match.ztIndex) - 1;
checkNaN(iX, iY, iZ);
ASSERT(this._currentMaterialName, 'unassigned material');
this._tris.push({
iX: iX,
iY: iY,
iZ: iZ,
iXUV: iUVx,
iYUV: iUVy,
iZUV: iUVz,
material: this._currentMaterialName,
const line = match.line.trim();
const vertices = line.split(' ').filter((x) => {
return x.length !== 0;
});
if (vertices.length < 3) {
throw new CustomError('Face data should have at least 3 vertices');
}
const points: {
positionIndex: number;
texcoordIndex?: number;
normalIndex?: number;
}[] = [];
for (const vertex of vertices) {
const vertexData = vertex.split('/');
switch (vertexData.length) {
case 1: {
const index = parseInt(vertexData[0]);
points.push({
positionIndex: index,
texcoordIndex: index,
normalIndex: index,
});
break;
}
case 2: {
const positionIndex = parseInt(vertexData[0]);
const texcoordIndex = parseInt(vertexData[1]);
points.push({
positionIndex: positionIndex,
texcoordIndex: texcoordIndex,
});
break;
}
case 3: {
const positionIndex = parseInt(vertexData[0]);
const texcoordIndex = parseInt(vertexData[1]);
const normalIndex = parseInt(vertexData[2]);
points.push({
positionIndex: positionIndex,
texcoordIndex: texcoordIndex,
normalIndex: normalIndex,
});
break;
}
default:
throw new CustomError(`Face data has unexpected number of vertex data: ${vertexData.length}`);
}
}
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.positionIndex - 1,
y: pointA.positionIndex - 1,
z: pointB.positionIndex - 1,
},
material: this._currentMaterialName,
};
if (pointBase.normalIndex || pointA.normalIndex || pointB.normalIndex) {
ASSERT(pointBase.normalIndex && pointA.normalIndex && pointB.normalIndex);
tri.normalIndices = {
x: pointBase.normalIndex - 1,
y: pointA.normalIndex - 1,
z: pointB.normalIndex - 1,
};
}
if (pointBase.texcoordIndex || pointA.texcoordIndex || pointB.texcoordIndex) {
ASSERT(pointBase.texcoordIndex && pointA.texcoordIndex && pointB.texcoordIndex);
tri.texcoordIndices = {
x: pointBase.texcoordIndex - 1,
y: pointA.texcoordIndex - 1,
z: pointB.texcoordIndex - 1,
};
}
this._tris.push(tri);
}
},
},
];
@ -153,7 +191,7 @@ export class ObjImporter extends IImporter {
private _mtlParsers = [
{
// e.g. 'newmtl my_material'
regex: new RegExpBuilder().add(/newmtl/).add(REGEX_NZ_ANY, 'name').toRegExp(),
regex: new RegExpBuilder().add(/^newmtl/).add(REGEX_NZ_ANY, 'name').toRegExp(),
delegate: (match: { [key: string]: string }) => {
this._addCurrentMaterial();
this._currentMaterialName = match.name.trim();
@ -164,7 +202,7 @@ export class ObjImporter extends IImporter {
{
// e.g. 'Kd 0.123 0.456 0.789'
regex: new RegExpBuilder()
.add(/Kd/)
.add(/^Kd/)
.addNonzeroWhitespace()
.add(REGEX_NUMBER, 'r')
.addNonzeroWhitespace()
@ -184,7 +222,7 @@ export class ObjImporter extends IImporter {
},
{
// e.g. 'map_Kd my/path/to/file.png'
regex: new RegExpBuilder().add(/map_Kd/).add(REGEX_NZ_ANY, 'path').toRegExp(),
regex: new RegExpBuilder().add(/^map_Kd/).add(REGEX_NZ_ANY, 'path').toRegExp(),
delegate: (match: { [key: string]: string }) => {
let mtlPath = match.path.trim();
if (!path.isAbsolute(mtlPath)) {
@ -197,7 +235,7 @@ export class ObjImporter extends IImporter {
},
];
override createMesh(filePath: string): Mesh {
override parseFile(filePath: string) {
ASSERT(path.isAbsolute(filePath), 'path not absolute');
this._objPath = path.parse(filePath);
@ -216,9 +254,10 @@ export class ObjImporter extends IImporter {
}
this._parseMTL();
}
LOG(this);
return new Mesh(this._vertices, this._uvs, this._tris, this._materials);
override toMesh(): Mesh {
return new Mesh(this._vertices, this._normals, this._uvs, this._tris, this._materials);
}
private _parseOBJ(path: string) {
@ -234,12 +273,12 @@ export class ObjImporter extends IImporter {
const fileLines = fileContents.split('\n');
for (const line of fileLines) {
this._parseOBJLine(line);
this.parseOBJLine(line);
}
}
private _parseOBJLine(line: string) {
const essentialTokens = ['mtllib ', 'usemtl ', 'v ', 'vt ', 'f '];
public parseOBJLine(line: string) {
const essentialTokens = ['mtllib ', 'usemtl ', 'v ', 'vt ', 'f ', 'vn '];
for (const parser of this._objParsers) {
const match = parser.regex.exec(line);

View File

@ -1,18 +1,21 @@
import { Vector3 } from './vector';
import { UV, Bounds, LOG, ASSERT, CustomError, LOG_WARN, Warnable, getRandomID } from './util';
import { UVTriangle } from './triangle';
import { Triangle, UVTriangle } from './triangle';
import { RGB } from './util';
import path from 'path';
import fs from 'fs';
interface VertexIndices {
x: number;
y: number;
z: number;
}
export interface Tri {
iX: number;
iY: number;
iZ: number;
iXUV: number;
iYUV: number;
iZUV: number;
positionIndices: VertexIndices;
texcoordIndices?: VertexIndices;
normalIndices?: VertexIndices;
material: string;
}
@ -24,7 +27,8 @@ export interface TexturedMaterial { path: string; type: MaterialType.textured }
export type MaterialMap = {[key: string]: (SolidMaterial | TexturedMaterial)};
export class Mesh extends Warnable {
public vertices!: Vector3[];
public vertices: Vector3[];
public normals!: Vector3[];
public uvs!: UV[];
public tris!: Tri[];
public materials!: MaterialMap;
@ -32,23 +36,23 @@ export class Mesh extends Warnable {
public static desiredHeight = 8.0;
constructor(vertices: Vector3[], uvs: UV[], tris: Tri[], materials: MaterialMap) {
constructor(vertices: Vector3[], normals: Vector3[], uvs: UV[], tris: Tri[], materials: MaterialMap) {
super();
LOG('New mesh');
this.vertices = vertices;
this.normals = normals;
this.uvs = uvs;
this.tris = tris;
this.materials = materials;
this.id = getRandomID();
}
public processMesh() {
this._checkMesh();
this._checkMaterials();
this._centreMesh();
this._scaleMesh();
LOG('Loaded mesh', this);
}
public getBounds() {
@ -71,6 +75,7 @@ export class Mesh extends Warnable {
}
// 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);
@ -79,6 +84,7 @@ export class Mesh extends Warnable {
uv.v = Math.abs(uv.v % 1);
}
}
*/
}
private _checkMaterials() {
@ -173,30 +179,56 @@ export class Mesh extends Warnable {
public getVertices(triIndex: number) {
const tri = this.tris[triIndex];
return {
v0: this.vertices[tri.iX],
v1: this.vertices[tri.iY],
v2: this.vertices[tri.iZ],
v0: this.vertices[tri.positionIndices.x],
v1: this.vertices[tri.positionIndices.y],
v2: this.vertices[tri.positionIndices.z],
};
}
public getUVs(triIndex: number) {
const tri = this.tris[triIndex];
if (tri.texcoordIndices) {
return {
uv0: this.uvs[tri.texcoordIndices.x] || new UV(0.0, 0.0),
uv1: this.uvs[tri.texcoordIndices.y] || new UV(0.0, 0.0),
uv2: this.uvs[tri.texcoordIndices.z] || new UV(0.0, 0.0),
};
}
return {
uv0: this.uvs[tri.iXUV],
uv1: this.uvs[tri.iYUV],
uv2: this.uvs[tri.iZUV],
uv0: new UV(0.0, 0.0),
uv1: new UV(0.0, 0.0),
uv2: new UV(0.0, 0.0),
};
}
public getNormals(triIndex: number) {
const vertexData = this.getVertices(triIndex);
const faceNormal = new Triangle(vertexData.v0, vertexData.v1, vertexData.v2).getNormal();
const tri = this.tris[triIndex];
if (tri.normalIndices) {
return {
v0: this.normals[tri.normalIndices.x] || faceNormal,
v1: this.normals[tri.normalIndices.y] || faceNormal,
v2: this.normals[tri.normalIndices.z] || faceNormal,
};
}
return {
v0: faceNormal,
v1: faceNormal,
v2: faceNormal,
};
}
public getUVTriangle(triIndex: number): UVTriangle {
const tri = this.tris[triIndex];
const vertices = this.getVertices(triIndex);
const texcoords = this.getUVs(triIndex);
return new UVTriangle(
this.vertices[tri.iX],
this.vertices[tri.iY],
this.vertices[tri.iZ],
this.uvs[tri.iXUV] || 0.0,
this.uvs[tri.iYUV] || 0.0,
this.uvs[tri.iZUV] || 0.0,
vertices.v0,
vertices.v1,
vertices.v2,
texcoords.uv0,
texcoords.uv1,
texcoords.uv2,
);
}
@ -241,6 +273,11 @@ export class Mesh extends Warnable {
newVertices[i] = this.vertices[i].copy();
}
const newNormals = new Array<Vector3>(this.normals.length);
for (let i = 0; i < this.normals.length; ++i) {
newNormals[i] = this.normals[i].copy();
}
const newUVs = new Array<UV>(this.uvs.length);
for (let i = 0; i < this.uvs.length; ++i) {
newUVs[i] = this.uvs[i].copy();
@ -268,7 +305,7 @@ export class Mesh extends Warnable {
};
}
return new Mesh(newVertices, newUVs, newTris, materials);
return new Mesh(newVertices, newNormals, newUVs, newTris, materials);
}
public getTriangleCount(): number {

174
test/obj_importer.test.ts Normal file
View File

@ -0,0 +1,174 @@
import { ObjImporter } from '../src/importers/obj_importer';
import { ASSERT } from '../src/util';
import { Vector3 } from '../src/vector';
test('Parse vertex #1', () => {
const importer = new ObjImporter();
importer.parseOBJLine('v 1.0 -2.0 3.0');
const mesh = importer.toMesh();
expect(mesh.vertices.length).toEqual(1);
expect(mesh.vertices[0].equals(new Vector3(1, -2, 3))).toBe(true);
});
test('Parse normal #1', () => {
const importer = new ObjImporter();
importer.parseOBJLine('vn -1.0 -0.5 0.0');
const mesh = importer.toMesh();
expect(mesh.normals.length).toEqual(1);
expect(mesh.normals[0].equals(new Vector3(-1, -0.5, 0))).toBe(true);
});
test('Parse texcoord #1', () => {
const importer = new ObjImporter();
importer.parseOBJLine('vt 0.5 -0.8');
const mesh = importer.toMesh();
expect(mesh.uvs.length).toEqual(1);
expect(mesh.uvs[0].u === 0.5 && mesh.uvs[0].v === -0.8).toBe(true);
});
test('Parse face #1', () => {
const importer = new ObjImporter();
importer.parseOBJLine('f 12 24 36');
const mesh = importer.toMesh();
expect(mesh.tris.length).toEqual(1);
const tri = mesh.tris[0];
expect(tri.texcoordIndices).toBeDefined(); ASSERT(tri.texcoordIndices);
expect(tri.normalIndices).toBeDefined(); ASSERT(tri.normalIndices);
expect(tri.positionIndices.x === 12 - 1 && tri.positionIndices.y === 24 - 1 && tri.positionIndices.z === 36 - 1).toBe(true);
expect(tri.texcoordIndices.x === 12 - 1 && tri.texcoordIndices.y === 24 - 1 && tri.texcoordIndices.z === 36 - 1).toBe(true);
expect(tri.normalIndices.x === 12 - 1 && tri.normalIndices.y === 24 - 1 && tri.normalIndices.z === 36 - 1).toBe(true);
});
test('Parse face #2', () => {
const importer = new ObjImporter();
importer.parseOBJLine('f 1/2 3/4 5/6');
const mesh = importer.toMesh();
expect(mesh.tris.length).toEqual(1);
const tri = mesh.tris[0];
expect(tri.texcoordIndices).toBeDefined(); ASSERT(tri.texcoordIndices);
expect(tri.normalIndices).toBeUndefined();
expect(tri.positionIndices.x === 1 - 1 && tri.positionIndices.y === 3 - 1 && tri.positionIndices.z === 5 - 1).toBe(true);
expect(tri.texcoordIndices.x === 2 - 1 && tri.texcoordIndices.y === 4 - 1 && tri.texcoordIndices.z === 6 - 1).toBe(true);
});
test('Parse face #3', () => {
const importer = new ObjImporter();
importer.parseOBJLine('f 11/2/3 4/55/6 7/8/99');
const mesh = importer.toMesh();
expect(mesh.tris.length).toEqual(1);
const tri = mesh.tris[0];
expect(tri.texcoordIndices).toBeDefined(); ASSERT(tri.texcoordIndices);
expect(tri.normalIndices).toBeDefined(); ASSERT(tri.normalIndices);
expect(tri.positionIndices.x === 11 - 1 && tri.positionIndices.y === 4 - 1&& tri.positionIndices.z === 7 - 1).toBe(true);
expect(tri.texcoordIndices.x === 2 - 1 && tri.texcoordIndices.y === 55 - 1 && tri.texcoordIndices.z === 8 - 1).toBe(true);
expect(tri.normalIndices.x === 3 - 1 && tri.normalIndices.y === 6 - 1 && tri.normalIndices.z === 99 - 1).toBe(true);
});
test('Parse mini #1', () => {
const importer = new ObjImporter();
importer.parseOBJLine('v -1 2 3');
importer.parseOBJLine('v 4 -5 6');
importer.parseOBJLine('v 7 8 -9');
importer.parseOBJLine('vn 0.0 0.1 0.2');
importer.parseOBJLine('vn 0.3 0.4 0.5');
importer.parseOBJLine('vn 0.6 0.7 0.8');
importer.parseOBJLine('vt 0.0 0.5');
importer.parseOBJLine('vt 0.5 1.0');
importer.parseOBJLine('vt 1.0 0.0');
importer.parseOBJLine('f 1 2 3');
const mesh = importer.toMesh();
expect(mesh.vertices.length).toEqual(3);
expect(mesh.uvs.length).toEqual(3);
expect(mesh.normals.length).toEqual(3);
expect(mesh.tris.length).toEqual(1);
const vertexData = mesh.getVertices(0);
expect(vertexData.v0.equals(new Vector3(-1, 2, 3))).toBe(true);
expect(vertexData.v1.equals(new Vector3(4, -5, 6))).toBe(true);
expect(vertexData.v2.equals(new Vector3(7, 8, -9))).toBe(true);
const texcoordData = mesh.getUVs(0);
expect(texcoordData.uv0.u === 0.0 && texcoordData.uv0.v === 0.5).toBe(true);
expect(texcoordData.uv1.u === 0.5 && texcoordData.uv1.v === 1.0).toBe(true);
expect(texcoordData.uv2.u === 1.0 && texcoordData.uv2.v === 0.0).toBe(true);
const normalData = mesh.getNormals(0);
expect(normalData.v0.equals(new Vector3(0.0, 0.1, 0.2))).toBe(true);
expect(normalData.v1.equals(new Vector3(0.3, 0.4, 0.5))).toBe(true);
expect(normalData.v2.equals(new Vector3(0.6, 0.7, 0.8))).toBe(true);
});
test('Parse mini #2', () => {
const importer = new ObjImporter();
importer.parseOBJLine('v -1 2 3');
importer.parseOBJLine('v 4 -5 6');
importer.parseOBJLine('v 7 8 -9');
importer.parseOBJLine('vn 0.0 0.1 0.2');
importer.parseOBJLine('vn 0.3 0.4 0.5');
importer.parseOBJLine('vn 0.6 0.7 0.8');
importer.parseOBJLine('vt 0.0 0.5');
importer.parseOBJLine('vt 0.5 1.0');
importer.parseOBJLine('vt 1.0 0.0');
importer.parseOBJLine('f 3/1/2 1/2/3 2/3/1');
const mesh = importer.toMesh();
expect(mesh.vertices.length).toEqual(3);
expect(mesh.uvs.length).toEqual(3);
expect(mesh.normals.length).toEqual(3);
expect(mesh.tris.length).toEqual(1);
const vertexData = mesh.getVertices(0);
expect(vertexData.v0.equals(new Vector3(7, 8, -9))).toBe(true);
expect(vertexData.v1.equals(new Vector3(-1, 2, 3))).toBe(true);
expect(vertexData.v2.equals(new Vector3(4, -5, 6))).toBe(true);
const texcoordData = mesh.getUVs(0);
expect(texcoordData.uv0.u === 0.0 && texcoordData.uv0.v === 0.5).toBe(true);
expect(texcoordData.uv1.u === 0.5 && texcoordData.uv1.v === 1.0).toBe(true);
expect(texcoordData.uv2.u === 1.0 && texcoordData.uv2.v === 0.0).toBe(true);
const normalData = mesh.getNormals(0);
expect(normalData.v0.equals(new Vector3(0.3, 0.4, 0.5))).toBe(true);
expect(normalData.v1.equals(new Vector3(0.6, 0.7, 0.8))).toBe(true);
expect(normalData.v2.equals(new Vector3(0.0, 0.1, 0.2))).toBe(true);
});
test('Parse mini #3', () => {
const importer = new ObjImporter();
importer.parseOBJLine('v 0 0 0');
importer.parseOBJLine('v 1 0 0');
importer.parseOBJLine('v 0 1 0');
importer.parseOBJLine('f 1 2 3');
const mesh = importer.toMesh();
expect(mesh.vertices.length).toEqual(3);
expect(mesh.uvs.length).toEqual(0);
expect(mesh.normals.length).toEqual(0);
expect(mesh.tris.length).toEqual(1);
const texcoordData = mesh.getUVs(0);
expect(texcoordData.uv0.u === 0.0 && texcoordData.uv0.v === 0.0).toBe(true);
expect(texcoordData.uv1.u === 0.0 && texcoordData.uv1.v === 0.0).toBe(true);
expect(texcoordData.uv2.u === 0.0 && texcoordData.uv2.v === 0.0).toBe(true);
const normalData = mesh.getNormals(0);
expect(normalData.v0.equals(new Vector3(0.0, 0.0, 1.0))).toBe(true);
expect(normalData.v1.equals(new Vector3(0.0, 0.0, 1.0))).toBe(true);
expect(normalData.v2.equals(new Vector3(0.0, 0.0, 1.0))).toBe(true);
});
test('Parse comments', () => {
const importer = new ObjImporter();
importer.parseOBJLine('# v -1 2 3');
importer.parseOBJLine('# vn 0.0 0.1 0.2');
importer.parseOBJLine('# vt 0.0 0.5');
importer.parseOBJLine('# f 1 1 1');
const mesh = importer.toMesh();
expect(mesh.vertices.length).toEqual(0);
expect(mesh.uvs.length).toEqual(0);
expect(mesh.normals.length).toEqual(0);
expect(mesh.tris.length).toEqual(0);
});