From f4ababcd332674600525a2eb5abb1684722c1116 Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Sat, 19 Mar 2022 00:36:32 +0000 Subject: [PATCH] Poly-face support, ObjImporter tests, parse normal data --- src/app_context.ts | 5 +- src/importer.ts | 3 +- src/importers/obj_importer.ts | 201 ++++++++++++++++++++-------------- src/mesh.ts | 89 ++++++++++----- test/obj_importer.test.ts | 174 +++++++++++++++++++++++++++++ 5 files changed, 363 insertions(+), 109 deletions(-) create mode 100644 test/obj_importer.test.ts diff --git a/src/app_context.ts b/src/app_context.ts index 9822c58..4a779a0 100644 --- a/src/app_context.ts +++ b/src/app_context.ts @@ -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); } diff --git a/src/importer.ts b/src/importer.ts index e2008ef..59e2104 100644 --- a/src/importer.ts +++ b/src/importer.ts @@ -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; } diff --git a/src/importers/obj_importer.ts b/src/importers/obj_importer.ts index 8b0bb88..12bad88 100644 --- a/src/importers/obj_importer.ts +++ b/src/importers/obj_importer.ts @@ -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); diff --git a/src/mesh.ts b/src/mesh.ts index 5f98dca..e25f787 100644 --- a/src/mesh.ts +++ b/src/mesh.ts @@ -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(this.normals.length); + for (let i = 0; i < this.normals.length; ++i) { + newNormals[i] = this.normals[i].copy(); + } + const newUVs = new Array(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 { diff --git a/test/obj_importer.test.ts b/test/obj_importer.test.ts new file mode 100644 index 0000000..a210f08 --- /dev/null +++ b/test/obj_importer.test.ts @@ -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); +});