diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp
index 4f0f1246d2..0aaf8772a2 100644
--- a/libraries/fbx/src/OBJReader.cpp
+++ b/libraries/fbx/src/OBJReader.cpp
@@ -31,6 +31,7 @@ public:
     };
     int nextToken();
     const QByteArray& getDatum() const { return _datum; }
+    bool isNextTokenFloat();
     void skipLine() { _device->readLine(); }
     void pushBackToken(int token) { _pushedBackToken = token; }
     void ungetChar(char ch) { _device->ungetChar(ch); }
@@ -91,14 +92,32 @@ int OBJTokenizer::nextToken() {
     return NO_TOKEN;
 }
 
+bool OBJTokenizer::isNextTokenFloat() {
+    if (nextToken() != OBJTokenizer::DATUM_TOKEN) {
+        return false;
+    }
+    QByteArray token = getDatum();
+    pushBackToken(OBJTokenizer::DATUM_TOKEN);
+    bool ok;
+    token.toFloat(&ok);
+    return ok;
+}
 
-bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeometry &geometry) {
+bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
+                   FBXGeometry &geometry, QVector<glm::vec3>& faceNormals, QVector<int>& faceNormalIndexes) {
     FBXMesh &mesh = geometry.meshes[0];
     mesh.parts.append(FBXMeshPart());
     FBXMeshPart &meshPart = mesh.parts.last();
     bool sawG = false;
     bool result = true;
 
+    meshPart.diffuseColor = glm::vec3(1, 1, 1);
+    meshPart.specularColor = glm::vec3(1, 1, 1);
+    meshPart.emissiveColor = glm::vec3(0, 0, 0);
+    meshPart.emissiveParams = glm::vec2(0, 1);
+    meshPart.shininess = 40;
+    meshPart.opacity = 1;
+
     meshPart.materialID = QString("dontknow") + QString::number(mesh.parts.count());
     meshPart.opacity = 1.0;
     meshPart._material = model::MaterialPointer(new model::Material());
@@ -131,11 +150,27 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom
                 break;
             }
             float x = std::stof(tokenizer.getDatum().data());
-            // notice the order of z and y -- in OBJ files, up is the 3rd value
+
+            if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
+                break;
+            }
+            float y = std::stof(tokenizer.getDatum().data());
+
             if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
                 break;
             }
             float z = std::stof(tokenizer.getDatum().data());
+
+            while (tokenizer.isNextTokenFloat()) {
+                // the spec(s) get(s) vague here.  might be w, might be a color... chop it off.
+                tokenizer.nextToken();
+            }
+            mesh.vertices.append(glm::vec3(x, y, z));
+        } else if (token == "vn") {
+            if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
+                break;
+            }
+            float x = std::stof(tokenizer.getDatum().data());
             if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
                 break;
             }
@@ -143,35 +178,74 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom
             if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
                 break;
             }
-            // the spec gets vague here.  might be w, might be a color... chop it off.
-            tokenizer.skipLine();
-            mesh.vertices.append(glm::vec3(x, y, z));
-            mesh.colors.append(glm::vec3(1, 1, 1));
+            float z = std::stof(tokenizer.getDatum().data());
+
+            while (tokenizer.isNextTokenFloat()) {
+                // the spec gets vague here.  might be w
+                tokenizer.nextToken();
+            }
+            faceNormals.append(glm::vec3(x, y, z));
         } else if (token == "f") {
             // a face can have 3 or more vertices
             QVector<int> indices;
+            QVector<int> normalIndices;
             while (true) {
-                if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { goto done; }
-                try {
-                    int vertexIndex = std::stoi(tokenizer.getDatum().data());
-                    // negative indexes count backward from the current end of the vertex list
-                    vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1);
-                    // obj index is 1 based
-                    assert(vertexIndex >= 1);
-                    indices.append(vertexIndex - 1);
+                if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
+                    if (indices.count() == 0) {
+                        goto done;
+                    }
+                    break;
                 }
-                catch(const std::exception& e) {
-                    // wasn't a number, but it back.
+                // faces can be:
+                //   vertex-index
+                //   vertex-index/texture-index
+                //   vertex-index/texture-index/surface-normal-index
+
+                QByteArray token = tokenizer.getDatum();
+                QList<QByteArray> parts = token.split('/');
+                assert(parts.count() >= 1);
+                assert(parts.count() <= 3);
+                QByteArray vertIndexBA = parts[ 0 ];
+
+                bool ok;
+                int vertexIndex = vertIndexBA.toInt(&ok);
+                if (!ok) {
+                    // it wasn't #/#/#, put it back and exit this loop.
                     tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
                     break;
                 }
+
+                // if (parts.count() > 1) {
+                //     QByteArray textureIndexBA = parts[ 1 ];
+                // }
+
+                if (parts.count() > 2) {
+                    QByteArray normalIndexBA = parts[ 2 ];
+                    bool ok;
+                    int normalIndex = normalIndexBA.toInt(&ok);
+                    if (ok) {
+                        normalIndices.append(normalIndex - 1);
+                    }
+                }
+
+                // negative indexes count backward from the current end of the vertex list
+                vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1);
+                // obj index is 1 based
+                assert(vertexIndex >= 1);
+                indices.append(vertexIndex - 1);
             }
 
             if (indices.count() == 3) {
-                // flip these around (because of the y/z swap above) so our triangles face outward
                 meshPart.triangleIndices.append(indices[0]);
-                meshPart.triangleIndices.append(indices[2]);
                 meshPart.triangleIndices.append(indices[1]);
+                meshPart.triangleIndices.append(indices[2]);
+                if (normalIndices.count() == 3) {
+                    faceNormalIndexes.append(normalIndices[0]);
+                    faceNormalIndexes.append(normalIndices[1]);
+                    faceNormalIndexes.append(normalIndices[2]);
+                } else {
+                    // hmm.
+                }
             } else if (indices.count() == 4) {
                 meshPart.quadIndices << indices;
             } else {
@@ -205,6 +279,10 @@ FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) {
 FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
     FBXGeometry geometry;
     OBJTokenizer tokenizer(device);
+    QVector<int> faceNormalIndexes;
+    QVector<glm::vec3> faceNormals;
+
+    faceNormalIndexes.clear();
 
     geometry.meshExtents.reset();
     geometry.meshes.append(FBXMesh());
@@ -216,7 +294,7 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
         // add a new meshPart to the geometry's single mesh.
         bool success = true;
         while (success) {
-            success = parseOBJGroup(tokenizer, mapping, geometry);
+            success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes);
         }
 
         FBXMesh &mesh = geometry.meshes[0];
@@ -229,31 +307,34 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
 
         geometry.joints.resize(1);
         geometry.joints[0].isFree = false;
-        // geometry.joints[0].freeLineage;
         geometry.joints[0].parentIndex = -1;
         geometry.joints[0].distanceToParent = 0;
         geometry.joints[0].boneRadius = 0;
         geometry.joints[0].translation = glm::vec3(0, 0, 0);
-        // geometry.joints[0].preTransform = ;
-        geometry.joints[0].preRotation = glm::quat(0, 0, 0, 1);
-        geometry.joints[0].rotation = glm::quat(0, 0, 0, 1);
-        geometry.joints[0].postRotation = glm::quat(0, 0, 0, 1);
-        // geometry.joints[0].postTransform = ;
-        // geometry.joints[0].transform = ;
         geometry.joints[0].rotationMin = glm::vec3(0, 0, 0);
         geometry.joints[0].rotationMax = glm::vec3(0, 0, 0);
-        geometry.joints[0].inverseDefaultRotation = glm::quat(0, 0, 0, 1);
-        geometry.joints[0].inverseBindRotation = glm::quat(0, 0, 0, 1);
-        // geometry.joints[0].bindTransform = ;
         geometry.joints[0].name = "OBJ";
         geometry.joints[0].shapePosition = glm::vec3(0, 0, 0);
-        geometry.joints[0].shapeRotation = glm::quat(0, 0, 0, 1);
         geometry.joints[0].shapeType = SPHERE_SHAPE;
-        geometry.joints[0].isSkeletonJoint = false;
+        geometry.joints[0].isSkeletonJoint = true;
+
+        geometry.jointIndices["x"] = 1;
+
+        FBXCluster cluster;
+        cluster.jointIndex = 0;
+        cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0,
+                                              0, 1, 0, 0,
+                                              0, 0, 1, 0,
+                                              0, 0, 0, 1);
+        mesh.clusters.append(cluster);
+
+        // The OBJ format has normals for faces.  The FBXGeometry structure has normals for points.
+        // run through all the faces, look-up (or determine) a normal and set the normal for the points
+        // that make up each face.
+        QVector<glm::vec3> pointNormalsSums;
 
-        // add bogus normal data for this mesh
         mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count());
-        mesh.tangents.fill(glm::vec3(0,0,0), mesh.vertices.count());
+        pointNormalsSums.fill(glm::vec3(0,0,0), mesh.vertices.count());
 
         foreach (FBXMeshPart meshPart, mesh.parts) {
             int triCount = meshPart.triangleIndices.count() / 3;
@@ -262,21 +343,43 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
                 int p1Index = meshPart.triangleIndices[i*3+1];
                 int p2Index = meshPart.triangleIndices[i*3+2];
 
-                glm::vec3 p0 = mesh.vertices[p0Index];
-                glm::vec3 p1 = mesh.vertices[p1Index];
-                glm::vec3 p2 = mesh.vertices[p2Index];
+                assert(p0Index < mesh.vertices.count());
+                assert(p1Index < mesh.vertices.count());
+                assert(p2Index < mesh.vertices.count());
 
-                glm::vec3 n = glm::cross(p1 - p0, p2 - p0);
-                glm::vec3 t = glm::cross(p2 - p0, n);
+                glm::vec3 n0, n1, n2;
+                if (i < faceNormalIndexes.count()) {
+                    int n0Index = faceNormalIndexes[i*3];
+                    int n1Index = faceNormalIndexes[i*3+1];
+                    int n2Index = faceNormalIndexes[i*3+2];
+                    n0 = faceNormals[n0Index];
+                    n1 = faceNormals[n1Index];
+                    n2 = faceNormals[n2Index];
+                } else {
+                    // We didn't read normals, add bogus normal data for this face
+                    glm::vec3 p0 = mesh.vertices[p0Index];
+                    glm::vec3 p1 = mesh.vertices[p1Index];
+                    glm::vec3 p2 = mesh.vertices[p2Index];
+                    n0 = glm::cross(p1 - p0, p2 - p0);
+                    n1 = n0;
+                    n2 = n0;
+                }
 
-                mesh.normals[p0Index] = n;
-                mesh.normals[p1Index] = n;
-                mesh.normals[p2Index] = n;
-
-                mesh.tangents[p0Index] = t;
-                mesh.tangents[p1Index] = t;
-                mesh.tangents[p2Index] = t;
+                // we sum up the normal for each point and then divide by the count to get an average
+                pointNormalsSums[p0Index] += n0;
+                pointNormalsSums[p1Index] += n1;
+                pointNormalsSums[p2Index] += n2;
             }
+
+            int vertCount = mesh.vertices.count();
+            for (int i = 0; i < vertCount; i++) {
+                float length = glm::length(pointNormalsSums[i]);
+                if (length > FLT_EPSILON) {
+                    mesh.normals[i] = glm::normalize(pointNormalsSums[i]);
+                }
+            }
+
+            // XXX do same normal calculation for quadCount
         }
     }
     catch(const std::exception& e) {
@@ -285,3 +388,77 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
 
     return geometry;
 }
+
+
+
+void fbxDebugDump(const FBXGeometry& fbxgeo) {
+    qDebug() << "---------------- fbxGeometry ----------------";
+    qDebug() << "  hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints;
+    qDebug() << "  offset =" << fbxgeo.offset;
+    qDebug() << "  attachments.count() = " << fbxgeo.attachments.count();
+    qDebug() << "  meshes.count() =" << fbxgeo.meshes.count();
+    foreach (FBXMesh mesh, fbxgeo.meshes) {
+        qDebug() << "    vertices.count() =" << mesh.vertices.count();
+        qDebug() << "    normals.count() =" << mesh.normals.count();
+        if (mesh.normals.count() == mesh.vertices.count()) {
+            for (int i = 0; i < mesh.normals.count(); i++) {
+                qDebug() << "        " << mesh.vertices[ i ] << mesh.normals[ i ];
+            }
+        }
+        qDebug() << "    tangents.count() =" << mesh.tangents.count();
+        qDebug() << "    colors.count() =" << mesh.colors.count();
+        qDebug() << "    texCoords.count() =" << mesh.texCoords.count();
+        qDebug() << "    texCoords1.count() =" << mesh.texCoords1.count();
+        qDebug() << "    clusterIndices.count() =" << mesh.clusterIndices.count();
+        qDebug() << "    clusterWeights.count() =" << mesh.clusterWeights.count();
+        qDebug() << "    meshExtents =" << mesh.meshExtents;
+        qDebug() << "    modelTransform =" << mesh.modelTransform;
+        qDebug() << "    parts.count() =" << mesh.parts.count();
+        foreach (FBXMeshPart meshPart, mesh.parts) {
+            qDebug() << "        quadIndices.count() =" << meshPart.quadIndices.count();
+            qDebug() << "        triangleIndices.count() =" << meshPart.triangleIndices.count();
+            qDebug() << "        diffuseColor =" << meshPart.diffuseColor;
+            qDebug() << "        specularColor =" << meshPart.specularColor;
+            qDebug() << "        emissiveColor =" << meshPart.emissiveColor;
+            qDebug() << "        emissiveParams =" << meshPart.emissiveParams;
+            qDebug() << "        shininess =" << meshPart.shininess;
+            qDebug() << "        opacity =" << meshPart.opacity;
+            qDebug() << "        materialID =" << meshPart.materialID;
+        }
+        qDebug() << "    clusters.count() =" << mesh.clusters.count();
+        foreach (FBXCluster cluster, mesh.clusters) {
+            qDebug() << "        jointIndex =" << cluster.jointIndex;
+            qDebug() << "        inverseBindMatrix =" << cluster.inverseBindMatrix;
+        }
+    }
+
+    qDebug() << "  jointIndices =" << fbxgeo.jointIndices;
+    qDebug() << "  joints.count() =" << fbxgeo.joints.count();
+
+    foreach (FBXJoint joint, fbxgeo.joints) {
+        qDebug() << "    isFree =" << joint.isFree;
+        qDebug() << "    freeLineage" << joint.freeLineage;
+        qDebug() << "    parentIndex" << joint.parentIndex;
+        qDebug() << "    distanceToParent" << joint.distanceToParent;
+        qDebug() << "    boneRadius" << joint.boneRadius;
+        qDebug() << "    translation" << joint.translation;
+        qDebug() << "    preTransform" << joint.preTransform;
+        qDebug() << "    preRotation" << joint.preRotation;
+        qDebug() << "    rotation" << joint.rotation;
+        qDebug() << "    postRotation" << joint.postRotation;
+        qDebug() << "    postTransform" << joint.postTransform;
+        qDebug() << "    transform" << joint.transform;
+        qDebug() << "    rotationMin" << joint.rotationMin;
+        qDebug() << "    rotationMax" << joint.rotationMax;
+        qDebug() << "    inverseDefaultRotation" << joint.inverseDefaultRotation;
+        qDebug() << "    inverseBindRotation" << joint.inverseBindRotation;
+        qDebug() << "    bindTransform" << joint.bindTransform;
+        qDebug() << "    name" << joint.name;
+        qDebug() << "    shapePosition" << joint.shapePosition;
+        qDebug() << "    shapeRotation" << joint.shapeRotation;
+        qDebug() << "    shapeType" << joint.shapeType;
+        qDebug() << "    isSkeletonJoint" << joint.isSkeletonJoint;
+    }
+
+    qDebug() << "\n";
+}
diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h
index 2ff55a9a61..8c7aa1aba6 100644
--- a/libraries/fbx/src/OBJReader.h
+++ b/libraries/fbx/src/OBJReader.h
@@ -4,3 +4,4 @@
 
 FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
 FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping);
+void fbxDebugDump(const FBXGeometry& fbxgeo);
diff --git a/tools/vhacd/src/VHACDUtilApp.cpp b/tools/vhacd/src/VHACDUtilApp.cpp
index 958a91dbfe..b4c141acae 100644
--- a/tools/vhacd/src/VHACDUtilApp.cpp
+++ b/tools/vhacd/src/VHACDUtilApp.cpp
@@ -50,15 +50,13 @@ bool writeOBJ(QString outFileName, QVector<QVector<VHACD::IVHACD::ConvexHull>>&
             for (unsigned int i = 0; i < hull.m_nPoints; i++) {
                 out << "v ";
                 out << formatFloat(hull.m_points[i*3]) << " ";
-                // swap y and z because up is 3rd value in OBJ
-                out << formatFloat(hull.m_points[i*3+2]) << " ";
-                out << formatFloat(hull.m_points[i*3+1]) << "\n";
+                out << formatFloat(hull.m_points[i*3+1]) << " ";
+                out << formatFloat(hull.m_points[i*3+2]) << "\n";
             }
             for (unsigned int i = 0; i < hull.m_nTriangles; i++) {
                 out << "f ";
-                // change order to flip normal (due to swapping y and z, above)
-                out << hull.m_triangles[i*3+1] + 1 + pointStartOffset  << " ";
                 out << hull.m_triangles[i*3] + 1 + pointStartOffset << " ";
+                out << hull.m_triangles[i*3+1] + 1 + pointStartOffset  << " ";
                 out << hull.m_triangles[i*3+2] + 1 + pointStartOffset  << "\n";
             }
             out << "\n";