• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ObjLoader()21 ObjLoader::ObjLoader() :
22     mPositionsStride(3), mNormalsStride(3), mTextureCoordsStride(2) {
23 
24 }
25 
isWhitespace(char c)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 
eatWhitespace(std::istream & is)37 void eatWhitespace(std::istream &is) {
38     while(is.good() && isWhitespace(is.peek())) {
39         is.get();
40     }
41 }
42 
getToken(std::istream & is,std::string & token)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 
appendDataFromStream(std::vector<float> & dataVec,uint32_t numFloats,std::istream & is)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 
checkNegativeIndex(int idx)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 
parseRawFaces()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 
checkNewMeshCreation(std::string & newGroup)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 
handleObjLine(char * line)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 
init(const char * fileName)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 
reIndexGeometry()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 
reIndexGeometryPrim(ObjMesh & mesh,PrimitiveVtx & prim)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