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