     // Init an objloader with the
     // source of the .obj file as a string
     //
-    init(source: String) {
+    public init(source: String, basePath: NSString) {
         scanner = ObjScanner(source: source)
+        self.basePath = basePath
     }
 
     // Read the specified source.
     // This operation is singled threaded and
     // should not be invoked again before
     // the call has returned
-    func read() throws -> [Shape] {
-        running = true
+    public func read() throws -> [Shape] {
         var shapes: [Shape] = []
 
         resetState()

        return marker.length == 1 && marker.characterAtIndex(0) == objectMarker
    }

    private static func isFace(marker: NSString) -> Bool {
        return marker.length == 1 && marker.characterAtIndex(0) == faceMarker
    }

    private func readVertex() throws -> [Double]? {

        }
    }

    private func parseMaterialFiles(filenames: [NSString]) throws {
        for filename in filenames {
            let fullPath = basePath.stringByAppendingPathComponent(filename as String)
            do {
                let fileContents = try NSString(contentsOfFile: fullPath,
                                        encoding: NSUTF8StringEncoding)
                let loader = MaterialLoader(source: fileContents as String,
                                            basePath: basePath)

                let materials = try loader.read()

                for material in materials {
                    materialCache[material.name] = material
                }

            } catch MaterialLoadingError.UnexpectedFileFormat(let msg) {
                throw ObjLoadingError.UnexpectedFileFormat(error: msg)
            } catch {
                throw ObjLoadingError.UnexpectedFileFormat(error: "Invalid material file at \(fullPath)")
            }
        }
    }

    private static func normalizeIndex(index: Int?, count: Int) -> Int? {
        guard let i = index else {
            return nil
