Skip to content

Commit

Permalink
Make obj file loading work for more things
Browse files Browse the repository at this point in the history
  • Loading branch information
davepagurek committed Nov 9, 2018
1 parent f0503b7 commit 4dda4ba
Show file tree
Hide file tree
Showing 10 changed files with 2,072 additions and 76,044 deletions.
197 changes: 75 additions & 122 deletions src/armature/Import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import { vec3 } from 'gl-matrix';

import { RGBColor } from '../colors/RGBColor';
import { Face, WorkingGeometry } from '../geometry/WorkingGeometry';
import { Material } from '../renderer/Material';
import { Material, MaterialParams } from '../renderer/Material';
import { GeometryNode, Node } from './Node';

import { flatten } from 'lodash';
import { mapValues } from 'lodash';

/**
* A helper class that specifies a group as stored in the obj files
*/
class Group {
public groupName: string;
public materialName: string;
public faces: Face[];
interface Group {
groupName: string;
materialName: string;
faces: Face[];
}

/**
Expand All @@ -26,30 +26,51 @@ class Data {
this.lines = rawData.split('\n');
}

public readLine() {
let line = this.lines[0].split(' ');
this.lines.shift();
// Skip comments and empty lines.
while (line.length < 1 || line[0].startsWith('#')) {
line = this.lines[0].split(' ');
this.lines.shift();
}

return line;
}

// TODO: Refactor to handle each line type individually and multipblex
// (so we're not reliant on a particular order)
public getLinesWithPrefix(prefix: string) {
const lines: string[][] = [];
while (this.lines[0].startsWith(prefix)) {
lines.push(this.readLine());
}

return lines;
public read() {
const vertices: vec3[] = [];
const normals: vec3[] = [];
const groups: Group[] = [];
let materialName = '';

while (this.lines.length > 0) {
const [command, ...args] = (<string>this.lines.shift()).split(/\s+/);

if (command === 'v') {
const vecLine = args.map(parseFloat)
const vec = vec3.fromValues(vecLine[0], vecLine[1], vecLine[2]);
vertices.push(vec);
} else if (command === 'vn') {
const vecLine = args.map(parseFloat)
const vec = vec3.fromValues(vecLine[0], vecLine[1], vecLine[2]);
normals.push(vec);
} else if (command === 'g') {
groups.push({ groupName: args[0], materialName, faces: [] });
} else if (command === 'f') {
const faceData: number[][] = args
.map((s: string) => s.split('/').map((i: string) => parseInt(i, 10)));

if (groups.length === 0) {
groups.push({ groupName: 'group', materialName, faces: [] });
}
const lastGroup = groups[groups.length - 1];

lastGroup.faces.push(new Face(
faceData.map((vertexInfo: number[]) => vertexInfo[0] - 1),
faceData.map((vertexInfo: number[]) => vertexInfo[2] - 1)
));
} else if (command === 'usemtl') {
materialName = args[0];
if (groups.length > 0) {
groups[groups.length - 1].materialName = materialName;
}
}
};

return {vertices, normals, groups};
}
}


/**
* Convert raw OBJData and MTLData from .obj and .mtl files into an array of nodes.
*
Expand All @@ -60,10 +81,7 @@ export function importObj(objData: string, mtlData: string) {
const materials = readMaterials(mtlData);
const data = new Data(objData);

readPreamble(data);
const vertices = readVertices(data);
const normals = readNormals(data);
const groups = readGroups(data);
const {vertices, normals, groups} = data.read();

const parent = new Node();
parent.setAnchor(vec3.fromValues(0, 0, 0));
Expand All @@ -74,7 +92,7 @@ export function importObj(objData: string, mtlData: string) {
vertices: vertices,
normals: normals,
faces: group.faces,
material: <Material>materials.get(group.materialName),
material: <Material>materials[group.materialName],
controlPoints: []
}),
parent
Expand All @@ -85,99 +103,34 @@ export function importObj(objData: string, mtlData: string) {
return [parent, ...geoNodes];
}

function readPreamble(data: Data) {
data.readLine(); // Read the obj file name
data.readLine(); // Read the mtl file name

return data;
}

function readVertices(data: Data) {
const vertices: vec3[] = [];
data.getLinesWithPrefix('v ').map((line: string[]) => {
const vecLine = line.map(parseFloat).slice(1);
const vec = vec3.fromValues(vecLine[0], vecLine[1], vecLine[2]);
vertices.push(vec);
});

return vertices;
}

function readNormals(data: Data) {
const normals: vec3[] = [];
data.getLinesWithPrefix('vn ').map((line: string[]) => {
const vecLine = line.map(parseFloat).slice(1);
const vec = vec3.fromValues(vecLine[0], vecLine[1], vecLine[2]);
normals.push(vec);
});

return normals;
}

function readGroups(data: Data) {
const groups: Group[] = [];
while (data.lines.length > 0 && data.lines[0].startsWith('g')) {
const groupName = data.lines[0].split(' ')[1];
data.lines = data.lines.slice(1);
const materialName = data.lines[0].split(' ')[1];
data.lines = data.lines.slice(1);
const faces: Face[] = [];
while (data.lines.length > 0 && data.lines[0].startsWith('f')) {
const index: string[][] = data.lines[0]
.split(' ')
.slice(1)
.map((s: string) => s.split('//'));
const face: number[] = flatten([...index])
.filter((_: string, idx: number) => idx % 2 === 0)
.map((s: string) => parseInt(s, 10) - 1);
faces.push(new Face(face));
data.lines = data.lines.slice(1);
}
groups.push({
groupName: groupName,
materialName: materialName,
faces: faces
});
}

return groups;
}

function readMaterials(mtlData: string) {
let lines = mtlData.split('\n');
const materials: Map<string, Material> = new Map<string, Material>();
while (lines.length > 0 && lines[0].startsWith('newmtl')) {
const materialName = lines[0].split(' ')[1];
lines = lines.slice(1);
// Assume the prefixes of the following lines are Ka, Kd, Ks, Ns, illum
lines[0]
.split(' ')
.slice(1)
.map(parseFloat); // ka
lines = lines.slice(1);
const kd = lines[0]
.split(' ')
.slice(1)
.map(parseFloat);
lines = lines.slice(1);
lines[0]
.split(' ')
.slice(1)
.map(parseFloat); // ks
lines = lines.slice(1);
const ns = parseFloat(lines[0].split(' ')[1]);
lines = lines.slice(1);
// Ignore the illumination for now
lines = lines.slice(1);

materials.set(
materialName,
Material.create({
color: RGBColor.fromRGB(kd[0] * 255, kd[1] * 255, kd[2] * 255),
shininess: ns
})
);
const lines = mtlData.split('\n');
const materials: {[name: string]: MaterialParams} = {};

let lastMaterial: MaterialParams | undefined;

while (lines.length > 0) {
const line = <string>lines.shift();
const [command, ...args] = line.split(/\s+/);

if (command === 'newmtl') {
const newMaterial = { color: RGBColor.fromRGB(0, 0, 0), shininess: 0 };
lastMaterial = newMaterial;
materials[args[0]] = newMaterial;
} else if (command === 'Kd') {
if (lastMaterial === undefined) {
throw new Error('Material data provided before declaring a material.');
}
lastMaterial.color = RGBColor.fromRGB(parseFloat(args[0])*255, parseFloat(args[1])*255, parseFloat(args[2])*255);
} else if (command === 'Ns') {
if (lastMaterial === undefined) {
throw new Error('Material data provided before declaring a material.');
}
lastMaterial.shininess = parseFloat(args[0]);
}
}

return materials;
return mapValues(materials, (params: MaterialParams) => Material.create(params));
}
3 changes: 1 addition & 2 deletions src/armature/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { mat3, mat4, vec3, vec4 } from 'gl-matrix';

import { Color } from '../colors/Color';
import { AABB } from '../geometry/BakedGeometry';
import { mtlData, objData } from '../import_dir/ImportData';
import { BakedMaterial } from '../renderer/Material';
import { RenderObject } from '../types/RenderObject';
import { worldSpaceAABB } from '../utils/aabb';
Expand Down Expand Up @@ -56,7 +55,7 @@ export class Model {
* @return {Model} model A model representing that described in .obj and .mtl format in the
* import_dir directory.
*/
public static importObj(): Model {
public static importObj(objData: string, mtlData: string): Model {
const nodes = importObj(objData, mtlData);

return new Model(nodes);
Expand Down
5 changes: 4 additions & 1 deletion src/examples/generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
Shape
} from '../calder';


import { mtlData, objData } from '../import_dir/ImportDataMonkey';

// tslint:disable-next-line:import-name
import Bezier = require('bezier-js');

Expand Down Expand Up @@ -248,7 +251,7 @@ renderer.eachFrame(draw);
const importBtn = document.createElement('button');
importBtn.innerText = 'Import';
importBtn.addEventListener('click', () => {
tree = Model.importObj();
tree = Model.importObj(objData, mtlData);
// leafModel = Model.importObj();
// tree = treeGen.generateSOSMC({
// start: 'branch',
Expand Down
33 changes: 17 additions & 16 deletions src/geometry/Shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ export namespace Shape {

const sphereField = new ScalarField(DIM, LENGTH, sphereSignedDistFunc);
const vertices = genIsoSurface(sphereField);
const faces = range(vertices.length / 3).map(
(i: number) => new Face(range(i * 3, (i + 1) * 3))
);
const faces = range(vertices.length / 3).map((i: number) => {
const indices = range(i * 3, (i + 1) * 3);
return new Face(indices, indices);
});

return new WorkingGeometry({
vertices: vertices,
Expand Down Expand Up @@ -99,8 +100,10 @@ export namespace Shape {
const faces: Face[] = [];
for (let i = 0; i < 6; i += 1) {
const base = i * 4;
faces.push(new Face([base, base + 1, base + 2]));
faces.push(new Face([base, base + 2, base + 3]));
const indicesSide1 = [base, base + 1, base + 2];
const indicesSide2 = [base, base + 2, base + 3];
faces.push(new Face(indicesSide1, indicesSide1));
faces.push(new Face(indicesSide2, indicesSide2));
}

return new WorkingGeometry({
Expand Down Expand Up @@ -177,7 +180,7 @@ export namespace Shape {
face.reverse();
}

faces.push(new Face(face));
faces.push(new Face(face, face));
});
});

Expand All @@ -197,9 +200,8 @@ export namespace Shape {
// |/
// -----p+i-----p+i+1--
// (p = PRECISION, which is # vertices in the ring)
faces.push(
new Face([offset + i, offset + (i + 1) % PRECISION, offset + PRECISION + i])
);
const indices = [offset + i, offset + (i + 1) % PRECISION, offset + PRECISION + i];
faces.push(new Face(indices, indices));

// Triangle 2:
// -------i-----i+1----
Expand All @@ -208,13 +210,12 @@ export namespace Shape {
// / |
// -----p+i-----p+i+1--
// (p = PRECISION, which is # vertices in the ring)
faces.push(
new Face([
offset + (i + 1) % PRECISION,
offset + PRECISION + (i + 1) % PRECISION,
offset + PRECISION + i
])
);
const indices2 = [
offset + (i + 1) % PRECISION,
offset + PRECISION + (i + 1) % PRECISION,
offset + PRECISION + i
];
faces.push(new Face(indices2, indices2));
});

return new WorkingGeometry({
Expand Down
Loading

0 comments on commit 4dda4ba

Please sign in to comment.