Home | History | Annotate | Download | only in a3dconvert
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "ObjLoader.h"
     18 #include <rsFileA3D.h>
     19 #include <sstream>
     20 
     21 ObjLoader::ObjLoader() :
     22     mPositionsStride(3), mNormalsStride(3), mTextureCoordsStride(2) {
     23 
     24 }
     25 
     26 bool isWhitespace(char c) {
     27     const char whiteSpace[] = { ' ', '\n', '\t', '\f', '\r' };
     28     const uint32_t numWhiteSpaceChars = 5;
     29     for (uint32_t i = 0; i < numWhiteSpaceChars; i ++) {
     30         if (whiteSpace[i] == c) {
     31             return true;
     32         }
     33     }
     34     return false;
     35 }
     36 
     37 void eatWhitespace(std::istream &is) {
     38     while(is.good() && isWhitespace(is.peek())) {
     39         is.get();
     40     }
     41 }
     42 
     43 bool getToken(std::istream &is, std::string &token) {
     44     eatWhitespace(is);
     45     token.clear();
     46     char c;
     47     while(is.good() && !isWhitespace(is.peek())) {
     48         c = is.get();
     49         if (is.good()){
     50             token += c;
     51         }
     52     }
     53     return token.size() > 0;
     54 }
     55 
     56 void appendDataFromStream(std::vector<float> &dataVec, uint32_t numFloats, std::istream &is) {
     57     std::string token;
     58     for (uint32_t i = 0; i < numFloats; i ++){
     59         bool valid = getToken(is, token);
     60         if (valid) {
     61             dataVec.push_back((float)atof(token.c_str()));
     62         } else {
     63             fprintf(stderr, "Encountered error reading geometry data");
     64             dataVec.push_back(0.0f);
     65         }
     66     }
     67 }
     68 
     69 bool checkNegativeIndex(int idx) {
     70     if(idx < 0) {
     71         fprintf(stderr, "Negative indices are not supported. Skipping face\n");
     72         return false;
     73     }
     74     return true;
     75 }
     76 
     77 void ObjLoader::parseRawFaces(){
     78     // We need at least a triangle
     79     if (mRawFaces.size() < 3) {
     80         return;
     81     }
     82 
     83     const char slash = '/';
     84     mParsedFaces.resize(mRawFaces.size());
     85     for (uint32_t i = 0; i < mRawFaces.size(); i ++) {
     86         size_t firstSeparator = mRawFaces[i].find_first_of(slash);
     87         size_t nextSeparator = mRawFaces[i].find_last_of(slash);
     88 
     89         // Use the string as a temp buffer to parse the index
     90         // Insert 0 instead of the slash to avoid substrings
     91         if (firstSeparator != std::string::npos) {
     92             mRawFaces[i][firstSeparator] = 0;
     93         }
     94         // Simple case, only one index
     95         int32_t vIdx = atoi(mRawFaces[i].c_str());
     96         // We do not support negative indices
     97         if (!checkNegativeIndex(vIdx)) {
     98             return;
     99         }
    100         // obj indices things beginning 1
    101         mParsedFaces[i].vertIdx = (uint32_t)vIdx - 1;
    102 
    103         if (nextSeparator != std::string::npos && nextSeparator != firstSeparator) {
    104             mRawFaces[i][nextSeparator] = 0;
    105             uint32_t nIdx = atoi(mRawFaces[i].c_str() + nextSeparator + 1);
    106             if (!checkNegativeIndex(nIdx)) {
    107                 return;
    108             }
    109             // obj indexes things beginning 1
    110             mParsedFaces[i].normIdx = (uint32_t)nIdx - 1;
    111         }
    112 
    113         // second case is where we have vertex and texture indices
    114         if (nextSeparator != std::string::npos &&
    115            (nextSeparator > firstSeparator + 1 || nextSeparator == firstSeparator)) {
    116             uint32_t tIdx = atoi(mRawFaces[i].c_str() + firstSeparator + 1);
    117             if (!checkNegativeIndex(tIdx)) {
    118                 return;
    119             }
    120             // obj indexes things beginning 1
    121             mParsedFaces[i].texIdx = (uint32_t)tIdx - 1;
    122         }
    123     }
    124 
    125     // Make sure a face list exists before we go adding to it
    126     if (mMeshes.back().mUnfilteredFaces.size() == 0) {
    127         mMeshes.back().appendUnfilteredFaces(mLastMtl);
    128     }
    129 
    130     // Now we have our parsed face, that we need to triangulate as necessary
    131     // Treat more complex polygons as fans.
    132     // This approach will only work only for convex polygons
    133     // but concave polygons need to be addressed elsewhere anyway
    134     for (uint32_t next = 1; next < mParsedFaces.size() - 1; next ++) {
    135         // push it to our current mesh
    136         mMeshes.back().mUnfilteredFaces.back().push_back(mParsedFaces[0]);
    137         mMeshes.back().mUnfilteredFaces.back().push_back(mParsedFaces[next]);
    138         mMeshes.back().mUnfilteredFaces.back().push_back(mParsedFaces[next + 1]);
    139     }
    140 }
    141 
    142 void ObjLoader::checkNewMeshCreation(std::string &newGroup) {
    143     // start a new mesh if we have some faces
    144     // accumulated on the current mesh.
    145     // It's possible to have multiple group statements
    146     // but we only care to actually start a new mesh
    147     // once we can have something we can draw on the previous one
    148     if (mMeshes.back().mUnfilteredFaces.size()) {
    149         mMeshes.push_back(ObjMesh());
    150     }
    151 
    152     mMeshes.back().mName = newGroup;
    153     printf("Converting vertex group: %s\n", newGroup.c_str());
    154 }
    155 
    156 void ObjLoader::handleObjLine(char *line) {
    157     const char* vtxToken    = "v";
    158     const char* normToken   = "vn";
    159     const char* texToken    = "vt";
    160     const char* groupToken  = "g";
    161     const char* mtlToken    = "usemtl";
    162     const char* faceToken   = "f";
    163 
    164     std::istringstream lineStream(line, std::istringstream::in);
    165 
    166     std::string token;
    167     bool valid = getToken(lineStream, token);
    168     if (!valid) {
    169         return;
    170     }
    171 
    172     if (token == vtxToken) {
    173         appendDataFromStream(mObjPositions, 3, lineStream);
    174     } else if (token == normToken) {
    175         appendDataFromStream(mObjNormals, 3, lineStream);
    176     } else if (token == texToken) {
    177         appendDataFromStream(mObjTextureCoords, 2, lineStream);
    178     } else if (token == groupToken) {
    179         valid = getToken(lineStream, token);
    180         checkNewMeshCreation(token);
    181     } else if (token == faceToken) {
    182         mRawFaces.clear();
    183         while(getToken(lineStream, token)) {
    184             mRawFaces.push_back(token);
    185         }
    186         parseRawFaces();
    187     }
    188     // Ignore materials for now
    189     else if (token == mtlToken) {
    190         valid = getToken(lineStream, token);
    191         mLastMtl = token;
    192 
    193         mMeshes.back().appendUnfilteredFaces(token);
    194     }
    195 }
    196 
    197 bool ObjLoader::init(const char *fileName) {
    198 
    199     std::ifstream ifs(fileName , std::ifstream::in);
    200     if (!ifs.good()) {
    201         fprintf(stderr, "Failed to read file %s.\n", fileName);
    202         return false;
    203     }
    204 
    205     mMeshes.clear();
    206 
    207     const uint32_t maxBufferSize = 2048;
    208     char *buffer = new char[maxBufferSize];
    209 
    210     mMeshes.push_back(ObjMesh());
    211 
    212     std::string token;
    213     bool isDone = false;
    214     while(!isDone) {
    215         ifs.getline(buffer, maxBufferSize);
    216         if (ifs.good() && ifs.gcount() > 0) {
    217             handleObjLine(buffer);
    218         } else {
    219             isDone = true;
    220         }
    221     }
    222 
    223     ifs.close();
    224     delete buffer;
    225 
    226     reIndexGeometry();
    227 
    228     return true;
    229 }
    230 
    231 void ObjLoader::reIndexGeometry() {
    232     // We want to know where each vertex lands
    233     mVertexRemap.resize(mObjPositions.size() / mPositionsStride);
    234 
    235     for (uint32_t m = 0; m < mMeshes.size(); m ++) {
    236         // clear the remap vector of old data
    237         for (uint32_t r = 0; r < mVertexRemap.size(); r ++) {
    238             mVertexRemap[r].clear();
    239         }
    240 
    241         for (uint32_t i = 0; i < mMeshes[m].mUnfilteredFaces.size(); i ++) {
    242             mMeshes[m].mTriangleLists[i].reserve(mMeshes[m].mUnfilteredFaces[i].size() * 2);
    243             for (uint32_t fI = 0; fI < mMeshes[m].mUnfilteredFaces[i].size(); fI ++) {
    244                 uint32_t newIndex = reIndexGeometryPrim(mMeshes[m], mMeshes[m].mUnfilteredFaces[i][fI]);
    245                 mMeshes[m].mTriangleLists[i].push_back(newIndex);
    246             }
    247         }
    248     }
    249 }
    250 
    251 uint32_t ObjLoader::reIndexGeometryPrim(ObjMesh &mesh, PrimitiveVtx &prim) {
    252 
    253     std::vector<float> &mPositions = mesh.mChannels[0].mData;
    254     std::vector<float> &mNormals = mesh.mChannels[1].mData;
    255     std::vector<float> &mTextureCoords = mesh.mChannels[2].mData;
    256 
    257     float posX = mObjPositions[prim.vertIdx * mPositionsStride + 0];
    258     float posY = mObjPositions[prim.vertIdx * mPositionsStride + 1];
    259     float posZ = mObjPositions[prim.vertIdx * mPositionsStride + 2];
    260 
    261     float normX = 0.0f;
    262     float normY = 0.0f;
    263     float normZ = 0.0f;
    264     if (prim.normIdx != MAX_INDEX) {
    265         normX = mObjNormals[prim.normIdx * mNormalsStride + 0];
    266         normY = mObjNormals[prim.normIdx * mNormalsStride + 1];
    267         normZ = mObjNormals[prim.normIdx * mNormalsStride + 2];
    268     }
    269 
    270     float texCoordX = 0.0f;
    271     float texCoordY = 0.0f;
    272     if (prim.texIdx != MAX_INDEX) {
    273         texCoordX = mObjTextureCoords[prim.texIdx * mTextureCoordsStride + 0];
    274         texCoordY = mObjTextureCoords[prim.texIdx * mTextureCoordsStride + 1];
    275     }
    276 
    277     std::vector<unsigned int> &ithRemapList = mVertexRemap[prim.vertIdx];
    278     // We may have some potential vertices we can reuse
    279     // loop over all the potential candidates and see if any match our guy
    280     for (unsigned int i = 0; i < ithRemapList.size(); i ++) {
    281 
    282         int ithRemap = ithRemapList[i];
    283         // compare existing vertex with the new one
    284         if (mPositions[ithRemap * mPositionsStride + 0] != posX ||
    285             mPositions[ithRemap * mPositionsStride + 1] != posY ||
    286             mPositions[ithRemap * mPositionsStride + 2] != posZ) {
    287             continue;
    288         }
    289 
    290         // Now go over normals
    291         if (prim.normIdx != MAX_INDEX) {
    292             if (mNormals[ithRemap * mNormalsStride + 0] != normX ||
    293                 mNormals[ithRemap * mNormalsStride + 1] != normY ||
    294                 mNormals[ithRemap * mNormalsStride + 2] != normZ) {
    295                 continue;
    296             }
    297         }
    298 
    299         // And texcoords
    300         if (prim.texIdx != MAX_INDEX) {
    301             if (mTextureCoords[ithRemap * mTextureCoordsStride + 0] != texCoordX ||
    302                 mTextureCoords[ithRemap * mTextureCoordsStride + 1] != texCoordY) {
    303                 continue;
    304             }
    305         }
    306 
    307         // If we got here the new vertex is identical to the one that we already stored
    308         return ithRemap;
    309     }
    310 
    311     // We did not encounter this vertex yet, store it and return its index
    312     mPositions.push_back(posX);
    313     mPositions.push_back(posY);
    314     mPositions.push_back(posZ);
    315 
    316     if (prim.normIdx != MAX_INDEX) {
    317         mNormals.push_back(normX);
    318         mNormals.push_back(normY);
    319         mNormals.push_back(normZ);
    320     }
    321 
    322     if (prim.texIdx != MAX_INDEX) {
    323         mTextureCoords.push_back(texCoordX);
    324         mTextureCoords.push_back(texCoordY);
    325     }
    326 
    327     // We need to remember this mapping. Since we are storing floats, not vec3's, need to
    328     // divide by position size to get the right index
    329     int currentVertexIndex = (mPositions.size()/mPositionsStride) - 1;
    330     ithRemapList.push_back(currentVertexIndex);
    331 
    332     return currentVertexIndex;
    333 }
    334