blob: 4465f4f1d85bb69184d274037fd0bbd67bcea1dc [file] [log] [blame]
Alex Sakhartchouk17bd28b2011-02-11 17:51:44 -08001/*
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
21ObjLoader::ObjLoader() :
22 mPositionsStride(3), mNormalsStride(3), mTextureCoordsStride(2) {
23
24}
25
26bool 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
37void eatWhitespace(std::istream &is) {
38 while(is.good() && isWhitespace(is.peek())) {
39 is.get();
40 }
41}
42
43bool 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
56void 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
69bool 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
77void 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
142void 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
156void 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
197bool 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
231bool ObjLoader::convertToA3D(const char *a3dFile) {
232 if (!getNumMeshes()) {
233 return false;
234 }
235 // Now write all this stuff out
236 Context rsc;
237 FileA3D file(&rsc);
238
239 for (uint32_t i = 0; i < getNumMeshes(); i ++) {
240 Mesh *exportedMesh = getMesh(&rsc, i);
241 file.appendToFile(exportedMesh);
242 delete exportedMesh;
243 }
244
245 file.writeFile(a3dFile);
246 return true;
247}
248
249void ObjLoader::reIndexGeometry() {
250 // We want to know where each vertex lands
251 mVertexRemap.resize(mObjPositions.size() / mPositionsStride);
252
253 for (uint32_t m = 0; m < mMeshes.size(); m ++) {
254 // clear the remap vector of old data
255 for (uint32_t r = 0; r < mVertexRemap.size(); r ++) {
256 mVertexRemap[r].clear();
257 }
258
259 for (uint32_t i = 0; i < mMeshes[m].mUnfilteredFaces.size(); i ++) {
260 mMeshes[m].mTriangleLists[i].reserve(mMeshes[m].mUnfilteredFaces[i].size() * 2);
261 for (uint32_t fI = 0; fI < mMeshes[m].mUnfilteredFaces[i].size(); fI ++) {
262 uint32_t newIndex = reIndexGeometryPrim(mMeshes[m], mMeshes[m].mUnfilteredFaces[i][fI]);
263 mMeshes[m].mTriangleLists[i].push_back(newIndex);
264 }
265 }
266 }
267}
268
269uint32_t ObjLoader::reIndexGeometryPrim(ObjMesh &mesh, PrimitiveVtx &prim) {
270
271 std::vector<float> &mPositions = mesh.mChannels[0].mData;
272 std::vector<float> &mNormals = mesh.mChannels[1].mData;
273 std::vector<float> &mTextureCoords = mesh.mChannels[2].mData;
274
275 float posX = mObjPositions[prim.vertIdx * mPositionsStride + 0];
276 float posY = mObjPositions[prim.vertIdx * mPositionsStride + 1];
277 float posZ = mObjPositions[prim.vertIdx * mPositionsStride + 2];
278
279 float normX = 0.0f;
280 float normY = 0.0f;
281 float normZ = 0.0f;
282 if (prim.normIdx != MAX_INDEX) {
283 normX = mObjNormals[prim.normIdx * mNormalsStride + 0];
284 normY = mObjNormals[prim.normIdx * mNormalsStride + 1];
285 normZ = mObjNormals[prim.normIdx * mNormalsStride + 2];
286 }
287
288 float texCoordX = 0.0f;
289 float texCoordY = 0.0f;
290 if (prim.texIdx != MAX_INDEX) {
291 texCoordX = mObjTextureCoords[prim.texIdx * mTextureCoordsStride + 0];
292 texCoordY = mObjTextureCoords[prim.texIdx * mTextureCoordsStride + 1];
293 }
294
295 std::vector<unsigned int> &ithRemapList = mVertexRemap[prim.vertIdx];
296 // We may have some potential vertices we can reuse
297 // loop over all the potential candidates and see if any match our guy
298 for (unsigned int i = 0; i < ithRemapList.size(); i ++) {
299
300 int ithRemap = ithRemapList[i];
301 // compare existing vertex with the new one
302 if (mPositions[ithRemap * mPositionsStride + 0] != posX ||
303 mPositions[ithRemap * mPositionsStride + 1] != posY ||
304 mPositions[ithRemap * mPositionsStride + 2] != posZ) {
305 continue;
306 }
307
308 // Now go over normals
309 if (prim.normIdx != MAX_INDEX) {
310 if (mNormals[ithRemap * mNormalsStride + 0] != normX ||
311 mNormals[ithRemap * mNormalsStride + 1] != normY ||
312 mNormals[ithRemap * mNormalsStride + 2] != normZ) {
313 continue;
314 }
315 }
316
317 // And texcoords
318 if (prim.texIdx != MAX_INDEX) {
319 if (mTextureCoords[ithRemap * mTextureCoordsStride + 0] != texCoordX ||
320 mTextureCoords[ithRemap * mTextureCoordsStride + 1] != texCoordY) {
321 continue;
322 }
323 }
324
325 // If we got here the new vertex is identical to the one that we already stored
326 return ithRemap;
327 }
328
329 // We did not encounter this vertex yet, store it and return its index
330 mPositions.push_back(posX);
331 mPositions.push_back(posY);
332 mPositions.push_back(posZ);
333
334 if (prim.normIdx != MAX_INDEX) {
335 mNormals.push_back(normX);
336 mNormals.push_back(normY);
337 mNormals.push_back(normZ);
338 }
339
340 if (prim.texIdx != MAX_INDEX) {
341 mTextureCoords.push_back(texCoordX);
342 mTextureCoords.push_back(texCoordY);
343 }
344
345 // We need to remember this mapping. Since we are storing floats, not vec3's, need to
346 // divide by position size to get the right index
347 int currentVertexIndex = (mPositions.size()/mPositionsStride) - 1;
348 ithRemapList.push_back(currentVertexIndex);
349
350 return currentVertexIndex;
351}