renderscript geometry converter.
Initial implementation of the converted from .obj and .dae file to the renderscript file format.

Change-Id: I47f4ecf0fb68515914b660dd3d548afa1920d79c
diff --git a/tools/a3dconvert/Android.mk b/tools/a3dconvert/Android.mk
new file mode 100644
index 0000000..7bc634e
--- /dev/null
+++ b/tools/a3dconvert/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Host executable
+include $(CLEAR_VARS)
+LOCAL_MODULE := a3dconvert
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS += -DANDROID_RS_SERIALIZE
+# Needed for colladaDom
+LOCAL_CFLAGS += -DNO_BOOST -DDOM_INCLUDE_TINYXML -DNO_ZAE
+
+LOCAL_SRC_FILES := \
+    a3dconvert.cpp \
+    ObjLoader.cpp \
+    ColladaConditioner.cpp \
+    ColladaGeometry.cpp \
+    ColladaLoader.cpp
+
+
+LOCAL_C_INCLUDES += external/collada/include
+LOCAL_C_INCLUDES += external/collada/include/1.4
+LOCAL_C_INCLUDES += frameworks/base/libs/rs
+
+LOCAL_LDLIBS := -lpthread
+LOCAL_STATIC_LIBRARIES += libRSserialize libutils libcutils
+LOCAL_STATIC_LIBRARIES += colladadom libtinyxml libpcrecpp libpcre
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/tools/a3dconvert/ColladaConditioner.cpp b/tools/a3dconvert/ColladaConditioner.cpp
new file mode 100644
index 0000000..0b0f694
--- /dev/null
+++ b/tools/a3dconvert/ColladaConditioner.cpp
@@ -0,0 +1,203 @@
+/*
+* Copyright 2006 Sony Computer Entertainment Inc.
+*
+* Licensed under the MIT Open Source License, for details please see license.txt or the website
+* http://www.opensource.org/licenses/mit-license.php
+*
+*/
+
+#include "ColladaConditioner.h"
+unsigned int ColladaConditioner::getMaxOffset( domInputLocalOffset_Array &input_array ) {
+
+    unsigned int maxOffset = 0;
+    for ( unsigned int i = 0; i < input_array.getCount(); i++ ) {
+        if ( input_array[i]->getOffset() > maxOffset ) {
+            maxOffset = (unsigned int)input_array[i]->getOffset();
+        }
+    }
+    return maxOffset;
+}
+
+void ColladaConditioner::createTrianglesFromPolylist( domMesh *thisMesh, domPolylist *thisPolylist ) {
+
+    // Create a new <triangles> inside the mesh that has the same material as the <polylist>
+    domTriangles *thisTriangles = (domTriangles *)thisMesh->createAndPlace("triangles");
+    //thisTriangles->setCount( 0 );
+    unsigned int triangles = 0;
+    thisTriangles->setMaterial(thisPolylist->getMaterial());
+    domP* p_triangles = (domP*)thisTriangles->createAndPlace("p");
+
+    // Give the new <triangles> the same <_dae> and <parameters> as the old <polylist>
+    for(int i=0; i<(int)(thisPolylist->getInput_array().getCount()); i++) {
+
+        thisTriangles->placeElement( thisPolylist->getInput_array()[i]->clone() );
+    }
+
+    // Get the number of inputs and primitives for the polygons array
+    int numberOfInputs = (int)getMaxOffset(thisPolylist->getInput_array()) + 1;
+    int numberOfPrimitives = (int)(thisPolylist->getVcount()->getValue().getCount());
+
+    unsigned int offset = 0;
+
+    // Triangulate all the primitives, this generates all the triangles in a single <p> element
+    for(int j = 0; j < numberOfPrimitives; j++) {
+
+        int triangleCount = (int)thisPolylist->getVcount()->getValue()[j] -2;
+        // Write out the primitives as triangles, just fan using the first element as the base
+        int idx = numberOfInputs;
+        for(int k = 0; k < triangleCount; k++) {
+            // First vertex
+            for(int l = 0; l < numberOfInputs; l++) {
+
+                p_triangles->getValue().append(thisPolylist->getP()->getValue()[offset + l]);
+            }
+            // Second vertex
+            for(int l = 0; l < numberOfInputs; l++) {
+
+                p_triangles->getValue().append(thisPolylist->getP()->getValue()[offset + idx + l]);
+            }
+            // Third vertex
+            idx += numberOfInputs;
+            for(int l = 0; l < numberOfInputs; l++) {
+
+                p_triangles->getValue().append(thisPolylist->getP()->getValue()[offset + idx + l]);
+            }
+            triangles++;
+        }
+        offset += (unsigned int)thisPolylist->getVcount()->getValue()[j] * numberOfInputs;
+    }
+    thisTriangles->setCount( triangles );
+
+}
+
+void ColladaConditioner::createTrianglesFromPolygons( domMesh *thisMesh, domPolygons *thisPolygons ) {
+
+    // Create a new <triangles> inside the mesh that has the same material as the <polygons>
+    domTriangles *thisTriangles = (domTriangles *)thisMesh->createAndPlace("triangles");
+    thisTriangles->setCount( 0 );
+    thisTriangles->setMaterial(thisPolygons->getMaterial());
+    domP* p_triangles = (domP*)thisTriangles->createAndPlace("p");
+
+    // Give the new <triangles> the same <_dae> and <parameters> as the old <polygons>
+    for(int i=0; i<(int)(thisPolygons->getInput_array().getCount()); i++) {
+
+        thisTriangles->placeElement( thisPolygons->getInput_array()[i]->clone() );
+    }
+
+    // Get the number of inputs and primitives for the polygons array
+    int numberOfInputs = (int)getMaxOffset(thisPolygons->getInput_array()) +1;
+    int numberOfPrimitives = (int)(thisPolygons->getP_array().getCount());
+
+    // Triangulate all the primitives, this generates all the triangles in a single <p> element
+    for(int j = 0; j < numberOfPrimitives; j++) {
+
+        // Check the polygons for consistancy (some exported files have had the wrong number of indices)
+        domP * thisPrimitive = thisPolygons->getP_array()[j];
+        int elementCount = (int)(thisPrimitive->getValue().getCount());
+        // Skip the invalid primitive
+        if((elementCount % numberOfInputs) != 0) {
+            continue;
+        } else {
+            int triangleCount = (elementCount/numberOfInputs)-2;
+            // Write out the primitives as triangles, just fan using the first element as the base
+            int idx = numberOfInputs;
+            for(int k = 0; k < triangleCount; k++) {
+                // First vertex
+                for(int l = 0; l < numberOfInputs; l++) {
+
+                    p_triangles->getValue().append(thisPrimitive->getValue()[l]);
+                }
+                // Second vertex
+                for(int l = 0; l < numberOfInputs; l++) {
+
+                    p_triangles->getValue().append(thisPrimitive->getValue()[idx + l]);
+                }
+                // Third vertex
+                idx += numberOfInputs;
+                for(int l = 0; l < numberOfInputs; l++) {
+
+                    p_triangles->getValue().append(thisPrimitive->getValue()[idx + l]);
+                }
+                thisTriangles->setCount(thisTriangles->getCount()+1);
+            }
+        }
+    }
+
+}
+
+
+bool ColladaConditioner::triangulate(DAE *dae) {
+
+    int error = 0;
+
+    // How many geometry elements are there?
+    int geometryElementCount = (int)(dae->getDatabase()->getElementCount(NULL, "geometry" ));
+
+    for(int currentGeometry = 0; currentGeometry < geometryElementCount; currentGeometry++) {
+
+        // Find the next geometry element
+        domGeometry *thisGeometry;
+        //      error = _dae->getDatabase()->getElement((daeElement**)&thisGeometry,currentGeometry, NULL, "geometry");
+        daeElement * element = 0;
+        error = dae->getDatabase()->getElement(&element,currentGeometry, NULL, "geometry");
+        thisGeometry = (domGeometry *) element;
+
+        // Get the mesh out of the geometry
+        domMesh *thisMesh = thisGeometry->getMesh();
+
+        if (thisMesh == NULL){
+            continue;
+        }
+
+        // Loop over all the polygon elements
+        for(int currentPolygons = 0; currentPolygons < (int)(thisMesh->getPolygons_array().getCount()); currentPolygons++) {
+
+            // Get the polygons out of the mesh
+            // Always get index 0 because every pass through this loop deletes the <polygons> element as it finishes with it
+            domPolygons *thisPolygons = thisMesh->getPolygons_array()[currentPolygons];
+            createTrianglesFromPolygons( thisMesh, thisPolygons );
+        }
+        while (thisMesh->getPolygons_array().getCount() > 0) {
+
+            domPolygons *thisPolygons = thisMesh->getPolygons_array().get(0);
+            // Remove the polygons from the mesh
+            thisMesh->removeChildElement(thisPolygons);
+        }
+        int polylistElementCount = (int)(thisMesh->getPolylist_array().getCount());
+        for(int currentPolylist = 0; currentPolylist < polylistElementCount; currentPolylist++) {
+
+            // Get the polylist out of the mesh
+            // Always get index 0 because every pass through this loop deletes the <polygons> element as it finishes with it
+            domPolylist *thisPolylist = thisMesh->getPolylist_array()[currentPolylist];
+            createTrianglesFromPolylist( thisMesh, thisPolylist );
+        }
+        while (thisMesh->getPolylist_array().getCount() > 0) {
+
+            domPolylist *thisPolylist = thisMesh->getPolylist_array().get(0);
+            // Remove the polylist from the mesh
+            thisMesh->removeChildElement(thisPolylist);
+        }
+    }
+    return (error == 0);
+}
+
+bool ColladaConditioner::triangulate(const char *inputFile) {
+
+    DAE dae;
+    bool convertSuceeded = true;
+    domCOLLADA* root = dae.open(inputFile);
+
+    if (!root) {
+        printf("Failed to read file %s.\n", inputFile);
+        return false;
+    }
+
+    convertSuceeded = triangulate(&dae);
+
+    dae.writeAll();
+    if(!convertSuceeded) {
+        printf("Encountered errors\n");
+    }
+
+    return convertSuceeded;
+}
\ No newline at end of file
diff --git a/tools/a3dconvert/ColladaConditioner.h b/tools/a3dconvert/ColladaConditioner.h
new file mode 100644
index 0000000..470b7b6
--- /dev/null
+++ b/tools/a3dconvert/ColladaConditioner.h
@@ -0,0 +1,28 @@
+/*
+* Copyright 2006 Sony Computer Entertainment Inc.
+*
+* Licensed under the MIT Open Source License, for details please see license.txt or the website
+* http://www.opensource.org/licenses/mit-license.php
+*
+*/
+
+#ifndef COLLADA_CONDITIONER
+#define COLLADA_CONDITIONER
+
+#include <dae.h>
+#include <dom/domConstants.h>
+#include <dom/domCOLLADA.h>
+
+class ColladaConditioner {
+
+private:
+    unsigned int getMaxOffset( domInputLocalOffset_Array &input_array );
+    void createTrianglesFromPolylist( domMesh *thisMesh, domPolylist *thisPolylist );
+    void createTrianglesFromPolygons( domMesh *thisMesh, domPolygons *thisPolygons );
+
+public:
+    bool triangulate(DAE *dae);
+    bool triangulate(const char *inputFile);
+};
+
+#endif //COLLADA_CONDITIONER
diff --git a/tools/a3dconvert/ColladaGeometry.cpp b/tools/a3dconvert/ColladaGeometry.cpp
new file mode 100644
index 0000000..1aba4f1
--- /dev/null
+++ b/tools/a3dconvert/ColladaGeometry.cpp
@@ -0,0 +1,319 @@
+/*
+* Copyright 2006 Sony Computer Entertainment Inc.
+*
+* Licensed under the MIT Open Source License, for details please see license.txt or the website
+* http://www.opensource.org/licenses/mit-license.php
+*
+*/
+
+#include "ColladaGeometry.h"
+#include <iostream>
+#include <sstream>
+
+ColladaGeometry::ColladaGeometry() :
+        mPositionFloats(NULL), mPositionOffset(-1),
+        mNormalFloats(NULL), mNormalOffset(-1),
+        mTangentFloats(NULL), mTangentOffset(-1),
+        mBinormalFloats(NULL), mBinormalOffset(-1),
+        mTexture1Floats(NULL), mTexture1Offset(-1),
+        mMultiIndexOffset(-1),
+        mPositionsStride(3), mNormalsStride(3),
+        mTextureCoordsStride(2), mTangentssStride(3), mBinormalsStride(3) {
+
+    mConvertedMesh.appendChannel("position", mPositionsStride);
+    mConvertedMesh.appendChannel("normal", mNormalsStride);
+    mConvertedMesh.appendChannel("texture0", mTextureCoordsStride);
+    mConvertedMesh.appendChannel("binormal", mBinormalsStride);
+    mConvertedMesh.appendChannel("tangent", mTangentssStride);
+
+    mPositions = &mConvertedMesh.mChannels[0].mData;
+    mNormals = &mConvertedMesh.mChannels[1].mData;
+    mTextureCoords = &mConvertedMesh.mChannels[2].mData;
+    mBinormals = &mConvertedMesh.mChannels[3].mData;
+    mTangents = &mConvertedMesh.mChannels[4].mData;
+}
+
+bool ColladaGeometry::init(domGeometryRef geometry) {
+
+    bool convertSuceeded = true;
+
+    const char* geoName = geometry->getName();
+    if (geoName == NULL) {
+        geoName = geometry->getId();
+    }
+    mConvertedMesh.mName = geoName;
+    mMesh = geometry->getMesh();
+
+    // Iterate over all the index groups and build up a simple resolved tri list and vertex array
+    const domTriangles_Array &allTriLists = mMesh->getTriangles_array();
+    int numTriLists = allTriLists.getCount();
+    mConvertedMesh.mTriangleLists.reserve(numTriLists);
+    mConvertedMesh.mTriangleListNames.reserve(numTriLists);
+    for (int i = 0; i < numTriLists; i ++) {
+        addTriangles(allTriLists[i]);
+    }
+
+    return convertSuceeded;
+}
+
+void ColladaGeometry::addTriangles(domTriangles * colladaTriangles) {
+
+    int numTriangles = colladaTriangles->getCount();
+    int triListIndex = mConvertedMesh.mTriangleLists.size();
+    mConvertedMesh.mTriangleLists.resize(triListIndex + 1);
+    std::string materialName = colladaTriangles->getMaterial();
+    if (materialName.size() == 0) {
+        char buffer[128];
+        sprintf(buffer, "index%d", triListIndex);
+        materialName = buffer;
+    }
+    mConvertedMesh.mTriangleListNames.push_back(materialName);
+
+    // It's a good idea to tell stl how much memory we intend to use
+    // to limit the number of reallocations
+    mPositions->reserve(numTriangles * 3);
+    mNormals->reserve(numTriangles * 3);
+    mTangents->reserve(numTriangles * 3);
+    mBinormals->reserve(numTriangles * 3);
+    mTextureCoords->reserve(numTriangles * 3);
+
+    // Stores the pointers to the image data and where in the tri list that data comes from
+    cacheOffsetsAndDataPointers(colladaTriangles);
+
+    // Collapse the multiindex that collada uses
+    const domListOfUInts &colladaIndexList = colladaTriangles->getP()->getValue();
+    std::vector<uint32_t> &a3dIndexList = mConvertedMesh.mTriangleLists[triListIndex];
+    a3dIndexList.resize(numTriangles * 3);
+    for (int i = 0; i < numTriangles * 3; i ++) {
+
+        a3dIndexList[i] = remapIndexAndStoreData(colladaIndexList, i);
+    }
+
+}
+
+void ColladaGeometry::cacheOffsetsAndDataPointers(domTriangles * colladaTriangles) {
+    // Define the names of known vertex channels
+    const char *positionSemantic = "POSITION";
+    const char *vertexSemantic = "VERTEX";
+    const char *normalSemantic = "NORMAL";
+    const char *tangentSemantic = "TANGENT";
+    const char *binormalSemantic = "BINORMAL";
+    const char *texture1Semantic = "TEXCOORD";
+
+    const domInputLocalOffset_Array &inputs = colladaTriangles->getInput_array();
+    mMultiIndexOffset = inputs.getCount();
+
+    // inputs with offsets
+    // There are two places collada can put links to our data
+    // 1 - in the VERTEX, which is its way of saying follow a link to the vertex structure
+    //     then every geometry array you find there is the same size as the position array
+    // 2 - a direct link to the channel from the primitive list. This tells us that there are
+    //     potentially more or less floats in those channels because there is some vertex re-use
+    //     or divergence in that data channel. For example, highly segmented uv set would produce a
+    //     larger array because for every physical vertex position thre might be 2 or more uv coords
+    for (uint32_t i = 0; i < inputs.getCount(); i ++) {
+
+        int currentOffset = inputs[i]->getOffset();
+        const char *currentSemantic = inputs[i]->getSemantic();
+
+        domSource * source = (domSource*) (domElement*) inputs[i]->getSource().getElement();
+        if (strcmp(vertexSemantic, currentSemantic) == 0) {
+            mPositionOffset = currentOffset;
+        }
+        else if (strcmp(normalSemantic, currentSemantic) == 0) {
+            mNormalOffset = currentOffset;
+            mNormalFloats = &source->getFloat_array()->getValue();
+        }
+        else if (strcmp(tangentSemantic, currentSemantic) == 0) {
+            mTangentOffset = currentOffset;
+            mTangentFloats = &source->getFloat_array()->getValue();
+        }
+        else if (strcmp(binormalSemantic, currentSemantic) == 0) {
+            mBinormalOffset = currentOffset;
+            mBinormalFloats = &source->getFloat_array()->getValue();
+        }
+        else if (strcmp(texture1Semantic, currentSemantic) == 0) {
+            mTexture1Offset = currentOffset;
+            mTexture1Floats = & source->getFloat_array()->getValue();
+        }
+    }
+
+    // There are multiple ways of getting to data, so follow them all
+    domVertices * vertices = mMesh->getVertices();
+    const domInputLocal_Array &verticesInputs = vertices->getInput_array();
+    for (uint32_t i = 0; i < verticesInputs.getCount(); i ++) {
+
+        const char *currentSemantic = verticesInputs[i]->getSemantic();
+
+        domSource * source = (domSource*) (domElement*) verticesInputs[i]->getSource().getElement();
+        if (strcmp(positionSemantic, currentSemantic) == 0) {
+            mPositionFloats = & source->getFloat_array()->getValue();
+            // TODO: Querry this from the accessor in the future because
+            // I supopose it's possible to have 4 floats if we hide something in w
+            int numberOfFloatsPerPoint = 3;
+            // We want to cllapse duplicate vertices, otherwise we could just unroll the tri list
+            mVertexRemap.resize(source->getFloat_array()->getCount()/numberOfFloatsPerPoint);
+        }
+        else if (strcmp(normalSemantic, currentSemantic) == 0) {
+            mNormalFloats = & source->getFloat_array()->getValue();
+            mNormalOffset = mPositionOffset;
+        }
+        else if (strcmp(tangentSemantic, currentSemantic) == 0) {
+            mTangentFloats = & source->getFloat_array()->getValue();
+            mTangentOffset = mPositionOffset;
+        }
+        else if (strcmp(binormalSemantic, currentSemantic) == 0) {
+            mBinormalFloats = & source->getFloat_array()->getValue();
+            mBinormalOffset = mPositionOffset;
+        }
+        else if (strcmp(texture1Semantic, currentSemantic) == 0) {
+            mTexture1Floats = & source->getFloat_array()->getValue();
+            mTexture1Offset = mPositionOffset;
+        }
+    }
+}
+
+int ColladaGeometry::remapIndexAndStoreData(const domListOfUInts &colladaIndexList, int indexToRemap) {
+
+    domUint positionIndex = colladaIndexList[indexToRemap*mMultiIndexOffset + mPositionOffset];
+
+    float posX = (*mPositionFloats)[positionIndex * mPositionsStride + 0];
+    float posY = (*mPositionFloats)[positionIndex * mPositionsStride + 1];
+    float posZ = (*mPositionFloats)[positionIndex * mPositionsStride + 2];
+
+    float normX = 0;
+    float normY = 0;
+    float normZ = 0;
+
+    if (mNormalOffset != -1) {
+        domUint normalIndex = colladaIndexList[indexToRemap*mMultiIndexOffset + mNormalOffset];
+        normX = (*mNormalFloats)[normalIndex * mNormalsStride + 0];
+        normY = (*mNormalFloats)[normalIndex * mNormalsStride + 1];
+        normZ = (*mNormalFloats)[normalIndex * mNormalsStride + 2];
+    }
+
+    float tanX = 0;
+    float tanY = 0;
+    float tanZ = 0;
+
+    if (mTangentOffset != -1) {
+        domUint tangentIndex = colladaIndexList[indexToRemap*mMultiIndexOffset + mTangentOffset];
+        tanX = (*mTangentFloats)[tangentIndex * mTangentssStride + 0];
+        tanY = (*mTangentFloats)[tangentIndex * mTangentssStride + 1];
+        tanZ = (*mTangentFloats)[tangentIndex * mTangentssStride + 2];
+    }
+
+    float binormX = 0;
+    float binormY = 0;
+    float binormZ = 0;
+
+    if (mBinormalOffset != -1) {
+        domUint binormalIndex = colladaIndexList[indexToRemap*mMultiIndexOffset + mNormalOffset];
+        binormX = (*mBinormalFloats)[binormalIndex * mBinormalsStride + 0];
+        binormY = (*mBinormalFloats)[binormalIndex * mBinormalsStride + 1];
+        binormZ = (*mBinormalFloats)[binormalIndex * mBinormalsStride + 2];
+    }
+
+    float texCoordX = 0;
+    float texCoordY = 0;
+
+    if (mTexture1Offset != -1) {
+        domUint texCoordIndex = colladaIndexList[indexToRemap*mMultiIndexOffset + mTexture1Offset];
+        texCoordX = (*mTexture1Floats)[texCoordIndex * mTextureCoordsStride + 0];
+        texCoordY = (*mTexture1Floats)[texCoordIndex * mTextureCoordsStride + 1];
+    }
+
+    std::vector<uint32_t> &ithRemapList = mVertexRemap[positionIndex];
+    // We may have some potential vertices we can reuse
+    // loop over all the potential candidates and see if any match our guy
+    for (uint32_t i = 0; i < ithRemapList.size(); i ++) {
+
+        int ithRemap = ithRemapList[i];
+        // compare existing vertex with the new one
+        if ((*mPositions)[ithRemap * mPositionsStride + 0] != posX ||
+            (*mPositions)[ithRemap * mPositionsStride + 1] != posY ||
+            (*mPositions)[ithRemap * mPositionsStride + 2] != posZ) {
+            continue;
+        }
+
+        // Now go over normals
+        if (mNormalOffset != -1) {
+            if ((*mNormals)[ithRemap * mNormalsStride + 0] != normX ||
+                (*mNormals)[ithRemap * mNormalsStride + 1] != normY ||
+                (*mNormals)[ithRemap * mNormalsStride + 2] != normZ) {
+                continue;
+            }
+        }
+
+        // Now go over tangents
+        if (mTangentOffset != -1) {
+            if ((*mTangents)[ithRemap * mTangentssStride + 0] != tanX ||
+                (*mTangents)[ithRemap * mTangentssStride + 1] != tanY ||
+                (*mTangents)[ithRemap * mTangentssStride + 2] != tanZ) {
+                continue;
+            }
+        }
+
+        // Now go over binormals
+        if (mBinormalOffset != -1) {
+            if ((*mBinormals)[ithRemap * mBinormalsStride + 0] != binormX ||
+                (*mBinormals)[ithRemap * mBinormalsStride + 1] != binormY ||
+                (*mBinormals)[ithRemap * mBinormalsStride + 2] != binormZ) {
+                continue;
+            }
+        }
+
+        // And texcoords
+        if (mTexture1Offset != -1) {
+            if ((*mTextureCoords)[ithRemap * mTextureCoordsStride + 0] != texCoordX ||
+                (*mTextureCoords)[ithRemap * mTextureCoordsStride + 1] != texCoordY) {
+               continue;
+            }
+        }
+
+        // If we got here the new vertex is identical to the one that we already stored
+        return ithRemap;
+    }
+
+    // We did not encounter this vertex yet, store it and return its index
+    mPositions->push_back(posX);
+    mPositions->push_back(posY);
+    mPositions->push_back(posZ);
+
+    if (mNormalOffset != -1) {
+        mNormals->push_back(normX);
+        mNormals->push_back(normY);
+        mNormals->push_back(normZ);
+    }
+
+    if (mTangentOffset != -1) {
+        mTangents->push_back(tanX);
+        mTangents->push_back(tanY);
+        mTangents->push_back(tanZ);
+    }
+
+    if (mBinormalOffset != -1) {
+        mBinormals->push_back(binormX);
+        mBinormals->push_back(binormY);
+        mBinormals->push_back(binormZ);
+    }
+
+    if (mTexture1Offset != -1) {
+        mTextureCoords->push_back(texCoordX);
+        mTextureCoords->push_back(texCoordY);
+    }
+
+    // We need to remember this mapping. Since we are storing floats, not vec3's, need to
+    // divide by position size to get the right index
+    int currentVertexIndex = (mPositions->size()/mPositionsStride) - 1;
+    ithRemapList.push_back(currentVertexIndex);
+
+    return currentVertexIndex;
+}
+
+
+
+
+
+
+
diff --git a/tools/a3dconvert/ColladaGeometry.h b/tools/a3dconvert/ColladaGeometry.h
new file mode 100644
index 0000000..5774997
--- /dev/null
+++ b/tools/a3dconvert/ColladaGeometry.h
@@ -0,0 +1,84 @@
+/*
+* Copyright 2006 Sony Computer Entertainment Inc.
+*
+* Licensed under the MIT Open Source License, for details please see license.txt or the website
+* http://www.opensource.org/licenses/mit-license.php
+*
+*/
+
+#ifndef _COLLADA_GEOMETRY_H_
+#define _COLLADA_GEOMETRY_H_
+
+#include <dae.h>
+#include <dom/domCOLLADA.h>
+#include <vector>
+#include <string>
+
+#include "rsContext.h"
+#include "rsMesh.h"
+#include "SimpleMesh.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+class ColladaGeometry {
+public:
+    ColladaGeometry();
+    bool init(domGeometryRef geometry);
+
+    Mesh *getMesh(Context *rsc) {
+        return mConvertedMesh.getMesh(rsc);
+    }
+
+private:
+
+    //Store some collada stuff
+    domMesh *mMesh;
+
+    // Cache the pointers to the collada version of the data
+    // This contains raw vertex data that is not necessarily the same size for all
+    // Offset refers to the way collada packs each triangle's index to position / normal / etc.
+    domListOfFloats *mPositionFloats;
+    int mPositionOffset;
+    domListOfFloats *mNormalFloats;
+    int mNormalOffset;
+    domListOfFloats *mTangentFloats;
+    int mTangentOffset;
+    domListOfFloats *mBinormalFloats;
+    int mBinormalOffset;
+    domListOfFloats *mTexture1Floats;
+    int mTexture1Offset;
+
+    // In the list of triangles, collada uses multiple indecies per triangle to point to the correct
+    // index in all the different arrays. We need to know the total number of these guys so we can
+    // just to the next triangle to process
+    int mMultiIndexOffset;
+
+    // All these vectors would contain the same number of "points"
+    // index*stride would properly get to the uv, normal etc.
+    // collada, like maya and many others keep point array, normal array etc
+    // different size in the cases the same vertex produces divergent normals for different faces
+    std::vector<float> *mPositions;
+    unsigned int mPositionsStride;
+    std::vector<float> *mNormals;
+    unsigned int mNormalsStride;
+    std::vector<float> *mTextureCoords;
+    unsigned int mTextureCoordsStride;
+    std::vector<float> *mTangents;
+    unsigned int mTangentssStride;
+    std::vector<float> *mBinormals;
+    unsigned int mBinormalsStride;
+
+    SimpleMesh mConvertedMesh;
+
+    // This vector is used to remap a position index into a list of all divergent vertices
+    std::vector<std::vector<unsigned int> > mVertexRemap;
+
+    void addTriangles(domTriangles * colladaTriangles);
+    void cacheOffsetsAndDataPointers(domTriangles * colladaTriangles);
+    int remapIndexAndStoreData(const domListOfUInts &colladaIndexList, int indexToRemap);
+
+};
+
+#endif //COLLADA_TO_A3D_GEOMETRY
diff --git a/tools/a3dconvert/ColladaLoader.cpp b/tools/a3dconvert/ColladaLoader.cpp
new file mode 100644
index 0000000..8a74748
--- /dev/null
+++ b/tools/a3dconvert/ColladaLoader.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ColladaLoader.h"
+#include "ColladaConditioner.h"
+#include "ColladaGeometry.h"
+#include "rsContext.h"
+#include "rsFileA3D.h"
+
+#include <dae.h>
+#include <dom/domCOLLADA.h>
+
+ColladaLoader::ColladaLoader() {
+
+}
+
+ColladaLoader::~ColladaLoader() {
+    clearGeometry();
+}
+
+void ColladaLoader::clearGeometry() {
+    for (uint32_t i = 0; i < mGeometries.size(); i++) {
+        delete mGeometries[i];
+    }
+    mGeometries.clear();
+}
+
+bool ColladaLoader::init(const char *colladaFile) {
+    DAE dae;
+
+    clearGeometry();
+
+    bool convertSuceeded = true;
+
+    domCOLLADA* root = dae.open(colladaFile);
+    if (!root) {
+        fprintf(stderr, "Failed to read file %s.\n", colladaFile);
+        return false;
+    }
+
+    // We only want to deal with triangulated meshes since rendering complex polygons is not feasible
+    ColladaConditioner conditioner;
+    conditioner.triangulate(&dae);
+
+    domLibrary_geometries *allGeometry = daeSafeCast<domLibrary_geometries>(root->getDescendant("library_geometries"));
+
+    if (allGeometry) {
+        convertSuceeded = convertAllGeometry(allGeometry) && convertSuceeded;
+    }
+
+    return convertSuceeded;
+}
+
+bool ColladaLoader::convertToA3D(const char *a3dFile) {
+    if (mGeometries.size() == 0) {
+        return false;
+    }
+    // Now write all this stuff out
+    Context rsc;
+    FileA3D file(&rsc);
+
+    for (uint32_t i = 0; i < mGeometries.size(); i++) {
+        Mesh *exportedMesh = mGeometries[i]->getMesh(&rsc);
+        file.appendToFile(exportedMesh);
+        delete exportedMesh;
+    }
+
+    file.writeFile(a3dFile);
+    return true;
+}
+
+bool ColladaLoader::convertAllGeometry(domLibrary_geometries *allGeometry) {
+
+    bool convertSuceeded = true;
+    domGeometry_Array &geo_array = allGeometry->getGeometry_array();
+    for (size_t i = 0; i < geo_array.getCount(); i++) {
+        domGeometry *geometry = geo_array[i];
+        const char *geometryName = geometry->getName();
+        if (geometryName == NULL) {
+            geometryName = geometry->getId();
+        }
+
+        domMeshRef mesh = geometry->getMesh();
+        if (mesh != NULL) {
+            printf("Converting geometry: %s\n", geometryName);
+            convertSuceeded = convertGeometry(geometry) && convertSuceeded;
+        } else {
+            printf("Skipping geometry: %s, unsupported type\n", geometryName);
+        }
+
+    }
+
+    return convertSuceeded;
+}
+
+bool ColladaLoader::convertGeometry(domGeometry *geometry) {
+    bool convertSuceeded = true;
+
+    domMeshRef mesh = geometry->getMesh();
+
+    ColladaGeometry *convertedGeo = new ColladaGeometry();
+    convertedGeo->init(geometry);
+
+    mGeometries.push_back(convertedGeo);
+
+    return convertSuceeded;
+}
diff --git a/tools/a3dconvert/ColladaLoader.h b/tools/a3dconvert/ColladaLoader.h
new file mode 100644
index 0000000..aa66e7d
--- /dev/null
+++ b/tools/a3dconvert/ColladaLoader.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _COLLADA_LOADER_H_
+#define _COLLADA_LOADER_H_
+
+#include <vector>
+
+class domLibrary_geometries;
+class domGeometry;
+class ColladaGeometry;
+
+class ColladaLoader {
+public:
+    ColladaLoader();
+    ~ColladaLoader();
+
+    bool init(const char *colladaFile);
+    bool convertToA3D(const char *a3dFile);
+
+private:
+    void clearGeometry();
+    std::vector<ColladaGeometry*> mGeometries;
+
+    bool convertAllGeometry(domLibrary_geometries *allGeometry);
+    bool convertGeometry(domGeometry *geometry);
+
+};
+
+#endif
\ No newline at end of file
diff --git a/tools/a3dconvert/ObjLoader.cpp b/tools/a3dconvert/ObjLoader.cpp
new file mode 100644
index 0000000..4465f4f
--- /dev/null
+++ b/tools/a3dconvert/ObjLoader.cpp
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ObjLoader.h"
+#include <rsFileA3D.h>
+#include <sstream>
+
+ObjLoader::ObjLoader() :
+    mPositionsStride(3), mNormalsStride(3), mTextureCoordsStride(2) {
+
+}
+
+bool isWhitespace(char c) {
+    const char whiteSpace[] = { ' ', '\n', '\t', '\f', '\r' };
+    const uint32_t numWhiteSpaceChars = 5;
+    for (uint32_t i = 0; i < numWhiteSpaceChars; i ++) {
+        if (whiteSpace[i] == c) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void eatWhitespace(std::istream &is) {
+    while(is.good() && isWhitespace(is.peek())) {
+        is.get();
+    }
+}
+
+bool getToken(std::istream &is, std::string &token) {
+    eatWhitespace(is);
+    token.clear();
+    char c;
+    while(is.good() && !isWhitespace(is.peek())) {
+        c = is.get();
+        if (is.good()){
+            token += c;
+        }
+    }
+    return token.size() > 0;
+}
+
+void appendDataFromStream(std::vector<float> &dataVec, uint32_t numFloats, std::istream &is) {
+    std::string token;
+    for (uint32_t i = 0; i < numFloats; i ++){
+        bool valid = getToken(is, token);
+        if (valid) {
+            dataVec.push_back((float)atof(token.c_str()));
+        } else {
+            fprintf(stderr, "Encountered error reading geometry data");
+            dataVec.push_back(0.0f);
+        }
+    }
+}
+
+bool checkNegativeIndex(int idx) {
+    if(idx < 0) {
+        fprintf(stderr, "Negative indices are not supported. Skipping face\n");
+        return false;
+    }
+    return true;
+}
+
+void ObjLoader::parseRawFaces(){
+    // We need at least a triangle
+    if (mRawFaces.size() < 3) {
+        return;
+    }
+
+    const char slash = '/';
+    mParsedFaces.resize(mRawFaces.size());
+    for (uint32_t i = 0; i < mRawFaces.size(); i ++) {
+        size_t firstSeparator = mRawFaces[i].find_first_of(slash);
+        size_t nextSeparator = mRawFaces[i].find_last_of(slash);
+
+        // Use the string as a temp buffer to parse the index
+        // Insert 0 instead of the slash to avoid substrings
+        if (firstSeparator != std::string::npos) {
+            mRawFaces[i][firstSeparator] = 0;
+        }
+        // Simple case, only one index
+        int32_t vIdx = atoi(mRawFaces[i].c_str());
+        // We do not support negative indices
+        if (!checkNegativeIndex(vIdx)) {
+            return;
+        }
+        // obj indices things beginning 1
+        mParsedFaces[i].vertIdx = (uint32_t)vIdx - 1;
+
+        if (nextSeparator != std::string::npos && nextSeparator != firstSeparator) {
+            mRawFaces[i][nextSeparator] = 0;
+            uint32_t nIdx = atoi(mRawFaces[i].c_str() + nextSeparator + 1);
+            if (!checkNegativeIndex(nIdx)) {
+                return;
+            }
+            // obj indexes things beginning 1
+            mParsedFaces[i].normIdx = (uint32_t)nIdx - 1;
+        }
+
+        // second case is where we have vertex and texture indices
+        if (nextSeparator != std::string::npos &&
+           (nextSeparator > firstSeparator + 1 || nextSeparator == firstSeparator)) {
+            uint32_t tIdx = atoi(mRawFaces[i].c_str() + firstSeparator + 1);
+            if (!checkNegativeIndex(tIdx)) {
+                return;
+            }
+            // obj indexes things beginning 1
+            mParsedFaces[i].texIdx = (uint32_t)tIdx - 1;
+        }
+    }
+
+    // Make sure a face list exists before we go adding to it
+    if (mMeshes.back().mUnfilteredFaces.size() == 0) {
+        mMeshes.back().appendUnfilteredFaces(mLastMtl);
+    }
+
+    // Now we have our parsed face, that we need to triangulate as necessary
+    // Treat more complex polygons as fans.
+    // This approach will only work only for convex polygons
+    // but concave polygons need to be addressed elsewhere anyway
+    for (uint32_t next = 1; next < mParsedFaces.size() - 1; next ++) {
+        // push it to our current mesh
+        mMeshes.back().mUnfilteredFaces.back().push_back(mParsedFaces[0]);
+        mMeshes.back().mUnfilteredFaces.back().push_back(mParsedFaces[next]);
+        mMeshes.back().mUnfilteredFaces.back().push_back(mParsedFaces[next + 1]);
+    }
+}
+
+void ObjLoader::checkNewMeshCreation(std::string &newGroup) {
+    // start a new mesh if we have some faces
+    // accumulated on the current mesh.
+    // It's possible to have multiple group statements
+    // but we only care to actually start a new mesh
+    // once we can have something we can draw on the previous one
+    if (mMeshes.back().mUnfilteredFaces.size()) {
+        mMeshes.push_back(ObjMesh());
+    }
+
+    mMeshes.back().mName = newGroup;
+    printf("Converting vertex group: %s\n", newGroup.c_str());
+}
+
+void ObjLoader::handleObjLine(char *line) {
+    const char* vtxToken    = "v";
+    const char* normToken   = "vn";
+    const char* texToken    = "vt";
+    const char* groupToken  = "g";
+    const char* mtlToken    = "usemtl";
+    const char* faceToken   = "f";
+
+    std::istringstream lineStream(line, std::istringstream::in);
+
+    std::string token;
+    bool valid = getToken(lineStream, token);
+    if (!valid) {
+        return;
+    }
+
+    if (token == vtxToken) {
+        appendDataFromStream(mObjPositions, 3, lineStream);
+    } else if (token == normToken) {
+        appendDataFromStream(mObjNormals, 3, lineStream);
+    } else if (token == texToken) {
+        appendDataFromStream(mObjTextureCoords, 2, lineStream);
+    } else if (token == groupToken) {
+        valid = getToken(lineStream, token);
+        checkNewMeshCreation(token);
+    } else if (token == faceToken) {
+        mRawFaces.clear();
+        while(getToken(lineStream, token)) {
+            mRawFaces.push_back(token);
+        }
+        parseRawFaces();
+    }
+    // Ignore materials for now
+    else if (token == mtlToken) {
+        valid = getToken(lineStream, token);
+        mLastMtl = token;
+
+        mMeshes.back().appendUnfilteredFaces(token);
+    }
+}
+
+bool ObjLoader::init(const char *fileName) {
+
+    std::ifstream ifs(fileName , std::ifstream::in);
+    if (!ifs.good()) {
+        fprintf(stderr, "Failed to read file %s.\n", fileName);
+        return false;
+    }
+
+    mMeshes.clear();
+
+    const uint32_t maxBufferSize = 2048;
+    char *buffer = new char[maxBufferSize];
+
+    mMeshes.push_back(ObjMesh());
+
+    std::string token;
+    bool isDone = false;
+    while(!isDone) {
+        ifs.getline(buffer, maxBufferSize);
+        if (ifs.good() && ifs.gcount() > 0) {
+            handleObjLine(buffer);
+        } else {
+            isDone = true;
+        }
+    }
+
+    ifs.close();
+    delete buffer;
+
+    reIndexGeometry();
+
+    return true;
+}
+
+bool ObjLoader::convertToA3D(const char *a3dFile) {
+    if (!getNumMeshes()) {
+        return false;
+    }
+    // Now write all this stuff out
+    Context rsc;
+    FileA3D file(&rsc);
+
+    for (uint32_t i = 0; i < getNumMeshes(); i ++) {
+        Mesh *exportedMesh = getMesh(&rsc, i);
+        file.appendToFile(exportedMesh);
+        delete exportedMesh;
+    }
+
+    file.writeFile(a3dFile);
+    return true;
+}
+
+void ObjLoader::reIndexGeometry() {
+    // We want to know where each vertex lands
+    mVertexRemap.resize(mObjPositions.size() / mPositionsStride);
+
+    for (uint32_t m = 0; m < mMeshes.size(); m ++) {
+        // clear the remap vector of old data
+        for (uint32_t r = 0; r < mVertexRemap.size(); r ++) {
+            mVertexRemap[r].clear();
+        }
+
+        for (uint32_t i = 0; i < mMeshes[m].mUnfilteredFaces.size(); i ++) {
+            mMeshes[m].mTriangleLists[i].reserve(mMeshes[m].mUnfilteredFaces[i].size() * 2);
+            for (uint32_t fI = 0; fI < mMeshes[m].mUnfilteredFaces[i].size(); fI ++) {
+                uint32_t newIndex = reIndexGeometryPrim(mMeshes[m], mMeshes[m].mUnfilteredFaces[i][fI]);
+                mMeshes[m].mTriangleLists[i].push_back(newIndex);
+            }
+        }
+    }
+}
+
+uint32_t ObjLoader::reIndexGeometryPrim(ObjMesh &mesh, PrimitiveVtx &prim) {
+
+    std::vector<float> &mPositions = mesh.mChannels[0].mData;
+    std::vector<float> &mNormals = mesh.mChannels[1].mData;
+    std::vector<float> &mTextureCoords = mesh.mChannels[2].mData;
+
+    float posX = mObjPositions[prim.vertIdx * mPositionsStride + 0];
+    float posY = mObjPositions[prim.vertIdx * mPositionsStride + 1];
+    float posZ = mObjPositions[prim.vertIdx * mPositionsStride + 2];
+
+    float normX = 0.0f;
+    float normY = 0.0f;
+    float normZ = 0.0f;
+    if (prim.normIdx != MAX_INDEX) {
+        normX = mObjNormals[prim.normIdx * mNormalsStride + 0];
+        normY = mObjNormals[prim.normIdx * mNormalsStride + 1];
+        normZ = mObjNormals[prim.normIdx * mNormalsStride + 2];
+    }
+
+    float texCoordX = 0.0f;
+    float texCoordY = 0.0f;
+    if (prim.texIdx != MAX_INDEX) {
+        texCoordX = mObjTextureCoords[prim.texIdx * mTextureCoordsStride + 0];
+        texCoordY = mObjTextureCoords[prim.texIdx * mTextureCoordsStride + 1];
+    }
+
+    std::vector<unsigned int> &ithRemapList = mVertexRemap[prim.vertIdx];
+    // We may have some potential vertices we can reuse
+    // loop over all the potential candidates and see if any match our guy
+    for (unsigned int i = 0; i < ithRemapList.size(); i ++) {
+
+        int ithRemap = ithRemapList[i];
+        // compare existing vertex with the new one
+        if (mPositions[ithRemap * mPositionsStride + 0] != posX ||
+            mPositions[ithRemap * mPositionsStride + 1] != posY ||
+            mPositions[ithRemap * mPositionsStride + 2] != posZ) {
+            continue;
+        }
+
+        // Now go over normals
+        if (prim.normIdx != MAX_INDEX) {
+            if (mNormals[ithRemap * mNormalsStride + 0] != normX ||
+                mNormals[ithRemap * mNormalsStride + 1] != normY ||
+                mNormals[ithRemap * mNormalsStride + 2] != normZ) {
+                continue;
+            }
+        }
+
+        // And texcoords
+        if (prim.texIdx != MAX_INDEX) {
+            if (mTextureCoords[ithRemap * mTextureCoordsStride + 0] != texCoordX ||
+                mTextureCoords[ithRemap * mTextureCoordsStride + 1] != texCoordY) {
+                continue;
+            }
+        }
+
+        // If we got here the new vertex is identical to the one that we already stored
+        return ithRemap;
+    }
+
+    // We did not encounter this vertex yet, store it and return its index
+    mPositions.push_back(posX);
+    mPositions.push_back(posY);
+    mPositions.push_back(posZ);
+
+    if (prim.normIdx != MAX_INDEX) {
+        mNormals.push_back(normX);
+        mNormals.push_back(normY);
+        mNormals.push_back(normZ);
+    }
+
+    if (prim.texIdx != MAX_INDEX) {
+        mTextureCoords.push_back(texCoordX);
+        mTextureCoords.push_back(texCoordY);
+    }
+
+    // We need to remember this mapping. Since we are storing floats, not vec3's, need to
+    // divide by position size to get the right index
+    int currentVertexIndex = (mPositions.size()/mPositionsStride) - 1;
+    ithRemapList.push_back(currentVertexIndex);
+
+    return currentVertexIndex;
+}
diff --git a/tools/a3dconvert/ObjLoader.h b/tools/a3dconvert/ObjLoader.h
new file mode 100644
index 0000000..210b35f
--- /dev/null
+++ b/tools/a3dconvert/ObjLoader.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _OBJ_LOADER_H_
+#define _OBJ_LOADER_H_
+
+#include <vector>
+#include <string>
+#include <iostream>
+#include <fstream>
+
+#include "SimpleMesh.h"
+#include <rsContext.h>
+
+using namespace android;
+using namespace android::renderscript;
+
+#define MAX_INDEX 0xffffffff
+
+class ObjLoader {
+public:
+    ObjLoader();
+    bool init(const char *objFile);
+    bool convertToA3D(const char *a3dFile);
+private:
+
+    Mesh *getMesh(Context *rsc, uint32_t meshIndex) {
+        return mMeshes[meshIndex].getMesh(rsc);
+    }
+    uint32_t getNumMeshes() const {
+        return mMeshes.size();
+    }
+
+    // .obj has a global list of vertex data
+    std::vector<float> mObjPositions;
+    std::vector<float> mObjNormals;
+    std::vector<float> mObjTextureCoords;
+
+    struct PrimitiveVtx {
+        uint32_t vertIdx;
+        uint32_t normIdx;
+        uint32_t texIdx;
+
+        PrimitiveVtx() : vertIdx(MAX_INDEX),
+                         normIdx(MAX_INDEX),
+                         texIdx(MAX_INDEX){
+        }
+    };
+
+    // Scratch buffer for faces
+    std::vector<std::string> mRawFaces;
+    std::vector<PrimitiveVtx> mParsedFaces;
+    std::string mLastMtl;
+
+    // Groups are used to separate multiple meshes within the same .obj file
+    class ObjMesh : public SimpleMesh {
+    public:
+
+        std::vector<std::vector<PrimitiveVtx> > mUnfilteredFaces;
+
+        void appendUnfilteredFaces(std::string name) {
+            appendFaceList(name);
+            mUnfilteredFaces.push_back(std::vector<PrimitiveVtx>());
+            // Reserve some space for index data
+            static const uint32_t numReserveIndecies = 128;
+            mUnfilteredFaces.back().reserve(numReserveIndecies);
+        }
+
+        ObjMesh() {
+            appendChannel("position", 3);
+            appendChannel("normal", 3);
+            appendChannel("texture0", 2);
+        }
+    };
+
+    std::vector<ObjMesh> mMeshes;
+    void checkNewMeshCreation(std::string &newGroup);
+
+    void parseRawFaces();
+    void handleObjLine(char *line);
+
+    void reIndexGeometry();
+    uint32_t reIndexGeometryPrim(ObjMesh &mesh, PrimitiveVtx &prim);
+
+    unsigned int mPositionsStride;
+    unsigned int mNormalsStride;
+    unsigned int mTextureCoordsStride;
+
+    // This vector is used to remap a position index into a list
+    //  of all divergent vertices
+    std::vector<std::vector<unsigned int> > mVertexRemap;
+};
+
+#endif //_OBJ_LOADER_H_
diff --git a/tools/a3dconvert/SimpleMesh.h b/tools/a3dconvert/SimpleMesh.h
new file mode 100644
index 0000000..57e1a7c
--- /dev/null
+++ b/tools/a3dconvert/SimpleMesh.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _SIMPLE_MESH_H_
+#define _SIMPLE_MESH_H_
+
+#include <rsContext.h>
+#include <rsMesh.h>
+using namespace android;
+using namespace android::renderscript;
+
+class SimpleMesh {
+public:
+    struct Channel {
+        std::vector<float> mData;
+        std::string mName;
+        uint32_t mStride;
+    };
+
+    // Vertex channels (position, normal)
+    // This assumes all the data array are the same size
+    std::vector<Channel> mChannels;
+
+    // Triangle list index data
+    std::vector<std::vector<uint32_t> > mTriangleLists;
+    // Names of all the triangle lists
+    std::vector<std::string> mTriangleListNames;
+    // Name of the entire object
+    std::string mName;
+
+    // Adds another index set to the mesh
+    void appendFaceList(std::string name) {
+        mTriangleListNames.push_back(name);
+        mTriangleLists.push_back(std::vector<uint32_t>());
+    }
+
+    // Adds another data channel (position, normal, etc.)
+    void appendChannel(std::string name, uint32_t stride) {
+        mChannels.push_back(Channel());
+        static const uint32_t reserveVtx = 128;
+        mChannels.back().mData.reserve(reserveVtx*stride);
+        mChannels.back().mName = name;
+        mChannels.back().mStride = stride;
+    }
+
+    SimpleMesh() {
+        // reserve some data in the vectors
+        // simply letting it grow by itself tends to waste a lot of time on
+        // rallocations / copies when dealing with geometry data
+        static const uint32_t reserveFaces = 8;
+        static const uint32_t reserveChannels = 8;
+        mTriangleLists.reserve(reserveFaces);
+        mTriangleListNames.reserve(reserveFaces);
+        mChannels.reserve(reserveChannels);
+    }
+
+    // Generates a renderscript mesh that could be used for a3d serialization
+    Mesh *getMesh(Context *rsc) {
+        if (mChannels.size() == 0) {
+            return NULL;
+        }
+
+        // Generate the element that describes our channel layout
+        rsc->mStateElement.elementBuilderBegin();
+        for (uint32_t c = 0; c < mChannels.size(); c ++) {
+            // Skip empty channels
+            if (mChannels[c].mData.size() == 0) {
+                continue;
+            }
+            const Element *subElem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, mChannels[c].mStride);
+            rsc->mStateElement.elementBuilderAdd(subElem, mChannels[c].mName.c_str(), 1);
+        }
+        const Element *vertexDataElem = rsc->mStateElement.elementBuilderCreate(rsc);
+
+        uint32_t numVerts = mChannels[0].mData.size()/mChannels[0].mStride;
+        Type *vertexDataType = Type::getType(rsc, vertexDataElem, numVerts, 0, 0, false, false);
+        vertexDataType->compute();
+
+        Allocation *vertexAlloc = new Allocation(rsc, vertexDataType, RS_ALLOCATION_USAGE_SCRIPT);
+
+        uint32_t vertexSize = vertexDataElem->getSizeBytes()/sizeof(float);
+        // Fill this allocation with some data
+        float *dataPtr = (float*)vertexAlloc->getPtr();
+        for (uint32_t i = 0; i < numVerts; i ++) {
+            // Find the pointer to the current vertex's data
+            uint32_t vertexPos = i*vertexSize;
+            float *vertexPtr = dataPtr + vertexPos;
+
+            for (uint32_t c = 0; c < mChannels.size(); c ++) {
+                // Skip empty channels
+                if (mChannels[c].mData.size() == 0) {
+                    continue;
+                }
+                for (uint32_t cStride = 0; cStride < mChannels[c].mStride; cStride ++) {
+                    *(vertexPtr++) = mChannels[c].mData[i * mChannels[c].mStride + cStride];
+                }
+            }
+        }
+
+        // Now lets write index data
+        const Element *indexElem = Element::create(rsc, RS_TYPE_UNSIGNED_16, RS_KIND_USER, false, 1);
+
+        Mesh *mesh = new Mesh(rsc);
+        mesh->setName(mName.c_str());
+        mesh->mVertexBufferCount = 1;
+        mesh->mVertexBuffers = new ObjectBaseRef<Allocation>[1];
+        mesh->mVertexBuffers[0].set(vertexAlloc);
+
+        mesh->mPrimitivesCount = mTriangleLists.size();
+        mesh->mPrimitives = new Mesh::Primitive_t *[mesh->mPrimitivesCount];
+
+        // load all primitives
+        for (uint32_t pCount = 0; pCount < mesh->mPrimitivesCount; pCount ++) {
+            Mesh::Primitive_t *prim = new Mesh::Primitive_t;
+            mesh->mPrimitives[pCount] = prim;
+
+            uint32_t numIndicies = mTriangleLists[pCount].size();
+            Type *indexType = Type::getType(rsc, indexElem, numIndicies, 0, 0, false, false );
+
+            indexType->compute();
+
+            Allocation *indexAlloc = new Allocation(rsc, indexType, RS_ALLOCATION_USAGE_SCRIPT);
+            uint16_t *indexPtr = (uint16_t*)indexAlloc->getPtr();
+            const std::vector<uint32_t> &indexList = mTriangleLists[pCount];
+            uint32_t numTries = numIndicies / 3;
+
+            for (uint32_t i = 0; i < numTries; i ++) {
+                indexPtr[i * 3 + 0] = (uint16_t)indexList[i * 3 + 0];
+                indexPtr[i * 3 + 1] = (uint16_t)indexList[i * 3 + 1];
+                indexPtr[i * 3 + 2] = (uint16_t)indexList[i * 3 + 2];
+            }
+            indexAlloc->setName(mTriangleListNames[pCount].c_str());
+            prim->mIndexBuffer.set(indexAlloc);
+            prim->mPrimitive = RS_PRIMITIVE_TRIANGLE;
+        }
+
+        return mesh;
+    }
+};
+
+#endif
diff --git a/tools/a3dconvert/a3dconvert.cpp b/tools/a3dconvert/a3dconvert.cpp
new file mode 100644
index 0000000..33f5733
--- /dev/null
+++ b/tools/a3dconvert/a3dconvert.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <vector>
+
+#include "ColladaLoader.h"
+#include "ObjLoader.h"
+
+int main (int argc, char * const argv[]) {
+    const char *objExt = ".obj";
+    const char *daeExt = ".dae";
+
+    if(argc != 3) {
+        printf("-----------------------------------------------------------------\n");
+        printf("Usage:\n");
+        printf("a3dconvert input_file a3d_output_file\n");
+        printf("Currently .obj and .dae (collada) input files are accepted\n");
+        printf("-----------------------------------------------------------------\n");
+        return 1;
+    }
+
+    bool isSuccessful = false;
+
+    std::string filename = argv[1];
+    size_t dotPos = filename.find_last_of('.');
+    if (dotPos == std::string::npos) {
+        printf("Invalid input. Currently .obj and .dae (collada) input files are accepted\n");
+        return 1;
+    }
+
+    std::string ext = filename.substr(dotPos);
+    if (ext == daeExt) {
+        ColladaLoader converter;
+        isSuccessful = converter.init(argv[1]);
+        if (isSuccessful) {
+            isSuccessful = converter.convertToA3D(argv[2]);
+        }
+    } else if (ext == objExt) {
+        ObjLoader objConv;
+        isSuccessful = objConv.init(argv[1]);
+        if (isSuccessful) {
+            isSuccessful = objConv.convertToA3D(argv[2]);
+        }
+    } else {
+        printf("Invalid input. Currently .obj and .dae (collada) input files are accepted\n");
+        return 1;
+    }
+
+    if(isSuccessful) {
+        printf("---All done---\n");
+    } else {
+        printf("---Encountered errors, conversion failed---\n");
+    }
+
+    return isSuccessful ? 0 : 1;
+}
diff --git a/tools/a3dconvert/license.txt b/tools/a3dconvert/license.txt
new file mode 100755
index 0000000..354667a
--- /dev/null
+++ b/tools/a3dconvert/license.txt
@@ -0,0 +1,25 @@
+Parts of this code come from colladaDom with the license below. The rest is AOSP.
+
+
+
+The MIT License

+

+Copyright 2006 Sony Computer Entertainment Inc.

+

+Permission is hereby granted, free of charge, to any person obtaining a copy

+of this software and associated documentation files (the "Software"), to deal

+in the Software without restriction, including without limitation the rights

+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+copies of the Software, and to permit persons to whom the Software is

+furnished to do so, subject to the following conditions:

+

+The above copyright notice and this permission notice shall be included in

+all copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

+THE SOFTWARE.