blob: 19b6e0b019636a8e46c2bcc9bdf24334cdc0f7b4 [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
Alex Sakhartchouk17bd28b2011-02-11 17:51:44 -0800231void 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
251uint32_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}