Skip to content

Commit

Permalink
Import .obj first iteration (#163)
Browse files Browse the repository at this point in the history
* Import .obj first iteration

* Clean up import methods into separate file

* Fixup, clean import obj

* Linter fixups

* Import small tree

* Rebase fixes

* Format

* Ignore input data in tests

* Don't collect coverage from import dir

* Try removing coverage

* Fix regexes

* Ahhh jest! Just ignoreeee itttt

* try again

* Flatten with lodash

* Remove coverage

* Add comment
  • Loading branch information
pbardea authored Nov 6, 2018
1 parent b9f6f22 commit f0503b7
Show file tree
Hide file tree
Showing 10 changed files with 151,464 additions and 17 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/import_dir/*
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ module.exports = {
collectCoverageFrom: [
'src/**/*.{ts,tsx,js,jsx}',
'!src/**/*.d.ts',
'!src/import_dir/*.ts',
],
coveragePathIgnorePatterns: [
'examples/',
'import_dir',
]
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"fix-format": "prettier --write '**/*.ts'",
"lint": "tslint -t stylish --project \"tsconfig.json\"",
"test": "npm run test-only",
"test-only": "jest --coverage",
"test-only": "jest",
"watch": "tsc -w -p tsconfig.build.json",
"webpack": "webpack"
},
Expand Down
183 changes: 183 additions & 0 deletions src/armature/Import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { vec3 } from 'gl-matrix';

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

import { flatten } 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[];
}

/**
* A class to store the string and read information of a multiline string.
*/
class Data {
public lines: string[];

constructor(rawData: string) {
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;
}
}

/**
* Convert raw OBJData and MTLData from .obj and .mtl files into an array of nodes.
*
* @return {Node[]} nodes An array of nodes representing the nodes in a modle
* as described in the OBJData and MTLData.
*/
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 parent = new Node();
parent.setAnchor(vec3.fromValues(0, 0, 0));
const geoNodes: GeometryNode[] = groups.map(
(group: Group) =>
new GeometryNode(
new WorkingGeometry({
vertices: vertices,
normals: normals,
faces: group.faces,
material: <Material>materials.get(group.materialName),
controlPoints: []
}),
parent
)
);
geoNodes.forEach((node: Node) => node.setAnchor(vec3.fromValues(0, 0, 0)));

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
})
);
}

return materials;
}
45 changes: 45 additions & 0 deletions src/armature/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ 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';
import { importObj } from './Import';
import { GeometryNode, Node } from './Node';
import { NodeRenderObject } from './NodeRenderObject';

Expand Down Expand Up @@ -48,6 +50,18 @@ export class Model {
this.nodes = nodes;
}

/**
* Read data from the import_dir and produce a new model.
*
* @return {Model} model A model representing that described in .obj and .mtl format in the
* import_dir directory.
*/
public static importObj(): Model {
const nodes = importObj(objData, mtlData);

return new Model(nodes);
}

/**
* Creates a new model.
*
Expand All @@ -65,6 +79,37 @@ export class Model {
return new Model([...this.nodes]);
}

/**
* @returns A deep copy of the current model that has all the same nodes, but can be added to.
*/
public cloneDeep() {
const parentToChildren: Map<Node, Node[]> = new Map<Node, Node[]>();
const nodeToClone: Map<Node, Node> = new Map<Node, Node>();
this.nodes.forEach((node: Node) => nodeToClone.set(node, node.clone()));
this.nodes.forEach((node: Node) => {
if (node.parent == null) {
return;
}
let children = parentToChildren.get(node.parent);
if (children == null) {
children = [];
}
children.push(<Node>nodeToClone.get(node));
parentToChildren.set(node.parent, children);
});
const nodeClones = this.nodes.map((node: Node) => {
const children = parentToChildren.get(node);
const clone = <Node>nodeToClone.get(node);
if (children != null) {
children.forEach((child: Node) => clone.addChild(child));
}

return clone;
});

return new Model([...nodeClones]);
}

/**
* Adds a node to the model.
*
Expand Down
32 changes: 30 additions & 2 deletions src/armature/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,14 +889,18 @@ export class GeometryNode extends Node {
* @param {matrix4} scale
*/
constructor(
geometry: WorkingGeometry,
geometry: WorkingGeometry | BakedGeometry,
parent: Node | null = null,
position: vector3 = vec3.fromValues(0, 0, 0),
rotation: matrix4 = mat4.create(),
scale: matrix4 = mat4.create()
) {
super(parent, position, rotation, scale);
this.geometry = geometry.bake();
if (geometry instanceof WorkingGeometry) {
this.geometry = geometry.bake();
} else {
this.geometry = geometry;
}
}

/**
Expand Down Expand Up @@ -931,6 +935,22 @@ export class GeometryNode extends Node {
callback(this);
}

public clone(): GeometryNode {
const cloned = new GeometryNode(
this.geometry,
this.parent,
this.getPosition(),
this.getRotation(),
this.getScale()
);
Object.keys(this.points).forEach((key: string) => {
cloned.createPoint(key, Mapper.vectorToCoord(this.points[key].position));
});
cloned.anchor = this.anchor;

return cloned;
}

public structureCallback(_: (node: Node) => void) {}
}

Expand Down Expand Up @@ -982,4 +1002,12 @@ export class Point {

return geometryNode;
}

public attachModel(modelRoot: Node, targetModelNodes: Node[]): Node[] {
modelRoot.setAnchor(vec3.fromValues(0, 0, 0));
modelRoot.setPosition(Mapper.vectorToCoord(this.position));
this.node.addChild(modelRoot);

return targetModelNodes;
}
}
Loading

0 comments on commit f0503b7

Please sign in to comment.