-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathObj.cpp
332 lines (299 loc) · 15.2 KB
/
Obj.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
//
// Created by rthier on 2016.04.08..
//
#include "objmasterlog.h"
#include "Obj.h"
#include "UseMtl.h"
#include "ObjectGroupElement.h"
#include <fstream>
#include <memory>
#include <vector>
#include <climits>
#include <map> /* for saveAs *.obj compacting with ordered operations */
#define MAT_SEP ":mtl:"
#define TECHNICAL_UNKNOWN_GROUP "__UNKNOWN__"
//#define DEBUG 1
namespace ObjMaster {
Obj::Obj(const AssetLibrary &assetLibrary, const char *path, const char *fileName) {
constructionHelper(assetLibrary, path, fileName, Obj::EXPECTED_VERTEX_DATA_NUM, Obj::EXPECTED_FACES_NUM);
}
Obj::Obj(const AssetLibrary &assetLibrary, const char *path, const char *fileName, int expectedVertexDataNum, int expectedFaceNum) {
constructionHelper(assetLibrary, path, fileName, expectedVertexDataNum, expectedFaceNum);
}
/** Save this MtlLib as a *.mtl - using the path, fileName and the provided asset-out library */
void Obj::saveAs(const AssetOutputLibrary &assetOutputLibrary, const char* path, const char* fileName, bool saveAsMtlToo, ObjSaveModeFlags saveMode) {
OMLOGI("Opening (obj) output stream for %s%s", path, fileName);
std::unique_ptr<std::ostream> output = assetOutputLibrary.getAssetOutputStream(path, fileName);
// saveAs our mtlLib if the caller wants that too and there is anything to write
// Also the mtl lib is not saved if the saveMode does not use materials at all
if(saveAsMtlToo && (((int)saveMode & ObjSaveModeFlags::MATERIALS_GEOMETRY) != 0)) {
// Generate proper *.mtl file name using the provided *.obj file name
// If there is .*obj extension, we change that to *.mtl if there were none, we just add...
std::string fn(fileName);
size_t lastIndex = fn.find_last_of(".");
std::string rawName;
if(lastIndex == std::string::npos) {
// There were no *.obj extension or any extension
rawName = fn;
} else {
// There were an extension - remove that
rawName = fn.substr(0, lastIndex);
}
// And *.mtl ending for getting the extension
std::string mtlFileName = rawName + ".mtl";
// Save the *.mtl file near the *.obj
mtlLib.saveAs(assetOutputLibrary, path, mtlFileName.c_str());
}
// Reference the (already existing or generated) *.mtl file(s) - if save mode saves materials
if(((int)saveMode & ObjSaveModeFlags::MATERIALS_GEOMETRY) != 0) {
auto mtlLibLine = mtlLib.asText();
output->write(mtlLibLine.c_str(), mtlLibLine.length())<<'\n';
}
// Write out all vertex data as 'v's
for(auto v : vs) {
auto line = v.asText();
output->write(line.c_str(), line.length())<<'\n';
}
// Write out all vertex data as 'vt's
for(auto vt : vts) {
auto line = vt.asText();
output->write(line.c_str(), line.length())<<'\n';
}
// Write out all vertex data as 'vn's
for(auto vn : vns) {
auto line = vn.asText();
output->write(line.c_str(), line.length())<<'\n';
}
// Write out faces, groups, materials
// ----------------------------------
if((int)saveMode == ObjSaveModeFlags::ONLY_UNGROUPED_GEOMETRY) {
// Simplest case: only geometry - just write out faces
for(auto f : fs) {
auto line = f.asText();
output->write(line.c_str(), line.length())<<'\n';
}
} else {
// For proper compact output generation we need the objMatFaceGroups sorted
std::map<std::string, ObjectMaterialFaceGroup> sortedObjectMaterialGroups;
bool gbit = false;
// The sorting key should be either matName:err:groupName or groupName:mtl:matName according to mode!
// This ensures that those face elements we can compact together are near each other!
for(auto kv : objectMaterialGroups) {
if(((int)saveMode == ObjSaveModeFlags::GROUPS_GEOMETRY) ||
((int)saveMode == ObjSaveModeFlags::MATERIALS_AND_GROUPS)) {
// set the gbit here
if(((int)saveMode & ObjSaveModeFlags::G) != 0){
gbit = true; // indicate 'g' usage
}
// groupName:mtl:matName - it is already available
// Rem.: Both above cases we need to sort by groups basically!
std::string key = kv.first;
sortedObjectMaterialGroups[key] = kv.second;
} else if((int)saveMode == ObjSaveModeFlags::MATERIALS_GEOMETRY) {
// matName:err:groupName
// Rem.: We use :err: deliberately to force ourself handling things as it should!
// We cannot just save out these keys when generating real output - only used for sorting!
std::string key = kv.second.textureDataHoldingMaterial.name + ":err:" + kv.second.objectGroupName;
sortedObjectMaterialGroups[key] = kv.second;
} else {
// This should never happen - unless someone breaks ObjMaster!
OMLOGE("Code is utterly broken! Unkown savemode!");
exit(0);
}
}
// Print out stuff that have the group or material for it
std::string currentMat(""); // material now used - USING EMPTY STRING IS NECESSARY! SEE BELOW LOOP FOR HANDLING UNGROUPED DATA!
std::string currentGrp(""); // group now used - USING EMPTY STRING IS NECESSARY HERE TOO!
int minStartFaceIndex = INT_MAX; // we do a min-search to see if there are faces that does not belong to any materials or groups!
constexpr int NO_MAT_FACE_GRP = INT_MAX; // If the minStartFaceIndex stays the INT_MAX it means there were no matFace groups at all!
for(auto skv : sortedObjectMaterialGroups) {
std::string &matName = skv.second.textureDataHoldingMaterial.name;
std::string &grpName = skv.second.objectGroupName;
// See if we need to print out groups or not - and if we need: then see if group has changed or not!
if((((int)saveMode & ObjSaveModeFlags::GROUPS_GEOMETRY) != 0) && (currentGrp != grpName)) {
// Write out the group
std::string line = ObjectGroupElement::asTextO(grpName);
if(gbit) {
line = ObjectGroupElement::asTextG(grpName);
}
output->write(line.c_str(), line.length())<<'\n';
// Erase current material (as we better always restart when the groups have changed!)
currentMat = "";
// Update current group
currentGrp = grpName;
}
// See if we need to print out materials or not - and if we need: then see if material has changed or not!
if((((int)saveMode & ObjSaveModeFlags::MATERIALS_GEOMETRY) != 0) && (currentMat != matName)) {
// Write out the group
std::string line = UseMtl::asText(matName);
output->write(line.c_str(), line.length())<<'\n';
// Update current group
currentMat = matName;
}
// Get where is this mat-face group - in which slice
int startFaceIndex = skv.second.faceIndex;
// We save the minimal start index we find
if(minStartFaceIndex > startFaceIndex) {
minStartFaceIndex = startFaceIndex;
}
int faceCount = skv.second.meshFaceCount;
// Print out the faces for this material-face group
for(int i = 0; i < faceCount; ++i) {
auto f = fs[startFaceIndex + i];
auto line = f.asText();
output->write(line.c_str(), line.length())<<'\n';
}
}
// (!)There might be faces not belonging to any group or material.
// We need to write them out somehow - and we add them to a new technical group...
// Rem.: we could add these before any 'o', 'g' or 'usemtl' as it should be - but that would slow us down considerably...
// Rem.: This problematic case never happens if the file is read-in with objmaster. Then the objMatFaceGroups are always
// generated for the non-grouped and non-materialized faces too - with an empty name. Because the empty name is
// the default empty setup above, that means that their faces are written out "automagically" well as it should
// (before any 'o', 'g', 'usemtl')! This is quite tricky, but visibly working - see the above code!
// The below code only handles the cases when the Obj object is manually constructed in-memory and has a bad layout.
// The ungrouped faces start from faceNo zero, and lasts until the minimum start face of the matFace groups
int ungroupmatFaceEndIndex = minStartFaceIndex;
if((ungroupmatFaceEndIndex != NO_MAT_FACE_GRP) && (ungroupmatFaceEndIndex > 0)) {
// Write out a technical group for these elements
std::string grpName(TECHNICAL_UNKNOWN_GROUP);
std::string line = ObjectGroupElement::asTextO(grpName);
if(gbit) {
line = ObjectGroupElement::asTextG(grpName);
}
output->write(line.c_str(), line.length())<<'\n';
// Simples case: only geometry - just write out faces until that point
for(int i = 0; i < ungroupmatFaceEndIndex; ++i) {
auto f = fs[i];
auto line = f.asText();
output->write(line.c_str(), line.length())<<'\n';
}
}
}
}
// Helper function for common constructor code-paths
void Obj::constructionHelper(const AssetLibrary &assetLibrary, const char *path, const char *fileName, int expectedVertexDataNum, int expectedFaceNum) {
// Save the path of the file that will be opened
objPath = std::string(path);
OMLOGI("Opening input stream for %s%s", path, fileName);
std::unique_ptr<std::istream> input = assetLibrary.getAssetStream(path, fileName);
OMLOGI("Initializing data vectors (expectedVertexDataNum:%d, expectedFaceNum:%d)", expectedVertexDataNum, expectedFaceNum);
// Initialize vectors with some meaningful default sizes
vs = std::vector<VertexElement>();
vs.reserve(expectedVertexDataNum);
vts = std::vector<VertexTextureElement>();
vts.reserve(expectedVertexDataNum);
vns = std::vector<VertexNormalElement>();
vns.reserve(expectedVertexDataNum);
fs = std::vector<FaceElement>();
fs.reserve(expectedFaceNum);
// We are holding the current material in this variable
// Can be updated by usemtl descriptors!
TextureDataHoldingMaterial currentMaterial;
// We are holding the name of the current object/group here. In case there is no group
// this can be safely the empty string.
std::string currentObjectGroupName;
// This holds the current pointer to the start of the faces in case of the material and/or
// object grouping...
int currentObjectMaterialFacesPointer = 0;
// We hold the pointer to the last of the faces for pointer arithmetics common to the
// various cases below
int currentLastFacesPointer = 0;
// Parse the given file line-by-line
OMLOGI("Reading obj data file line-by-line");
char line[DEFAULT_LINE_PARSE_LEN];
while(input->getline(line, DEFAULT_LINE_PARSE_LEN)) {
currentLastFacesPointer =( int)fs.size();;
if(VertexElement::isParsable(line)) {
// v
VertexElement v = VertexElement((const char*)line);
vs.push_back(v);
#ifdef DEBUG
OMLOGI(" - Added VertexElement: (%f, %f, %f)", v.x, v.y, v.z);
#endif
} else if(VertexTextureElement::isParsable(line)) {
// vt
vts.push_back(VertexTextureElement((const char*)line));
} else if(VertexNormalElement::isParsable(line)) {
// vn
vns.push_back(VertexNormalElement((const char*)line));
} else if(FaceElement::isParsable(line)) {
// TODO: implement N-gons here
// f
fs.push_back(FaceElement((const char*)line));
} else if(MtlLib::isParsable(line)) {
// mtllib
mtlLib = MtlLib(line, path, assetLibrary);
} else if(UseMtl::isParsable(line)) {
// usemtl
// End the collection of the currentObjectMaterialFaceGroup
extendObjectMaterialGroups(currentObjectGroupName,
currentMaterial,
currentObjectMaterialFacesPointer,
currentLastFacesPointer - currentObjectMaterialFacesPointer);
// Rem.: This copy is cheap as it does not contain texture data etc!
currentMaterial = mtlLib.getNonLoadedMaterialFor(UseMtl::fetchMtlName(line));
#ifdef DEBUG
OMLOGI(" - Using current-material: %s", currentMaterial.name);
#endif
// Set the current face start pointer to the current position
// so that the faces will be "collected" for the group
// BEWARE: This let us overindex the array if no faces are coming!!!
// We need to check this overindexint below!
currentObjectMaterialFacesPointer = (int)fs.size();
} else if(ObjectGroupElement::isParsable(line)) {
// o
// End the collection of the currentObjectMaterialFaceGroup
extendObjectMaterialGroups(currentObjectGroupName,
currentMaterial,
currentObjectMaterialFacesPointer,
currentLastFacesPointer - currentObjectMaterialFacesPointer);
currentObjectGroupName = ObjectGroupElement::getObjectGroupName(line);
#ifdef DEBUG
OMLOGI(" - Start of object group: %s", currentObjectGroupName);
#endif
// Set the current face start pointer to the current position
// so that the faces will be "collected" for the group
// BEWARE: This let us overindex the array if no faces are coming!!!
// We need to check this overindexint below!
currentObjectMaterialFacesPointer = (int)fs.size();
} else {
OMLOGW("Cannot parse line: %s", line);
}
}
// End the collection of the currentObjectMaterialFaceGroup by extending with the elements
// of the last obj/material group (and pointer update is necessary here too!)
currentLastFacesPointer = (int)fs.size();
extendObjectMaterialGroups(currentObjectGroupName,
currentMaterial,
currentObjectMaterialFacesPointer,
currentLastFacesPointer - currentObjectMaterialFacesPointer);
OMLOGI("Finished loading of Obj data.");
OMLOGI(" - Read vertices: %d", (int)vs.size());
OMLOGI(" - Read vertex-textures: %d", (int)vts.size());
OMLOGI(" - Read vertex-normals: %d", (int)vns.size());
OMLOGI(" - Read faces: %d", (int)fs.size());
OMLOGI(" - Read materials: %d", mtlLib.getMaterialCount());
OMLOGI(" - Read object/material groups: %d", (int)objectMaterialGroups.size());
}
/**
* Helper method used to extend the material face groups with the given data.
*/
void Obj::extendObjectMaterialGroups(std::string ¤tObjectGroupName,
TextureDataHoldingMaterial ¤tMaterial,
int currentObjectMaterialFacesPointer,
int sizeOfFaceStripe) {
// If the size is zero, we are not saving the group
// this is not only an optimization, but this is how we handle mtllib ...; o ... after each
// other (so that we are not creating a lot of empty and unnecessary elements!)
if (sizeOfFaceStripe > 0) {
this->objectMaterialGroups[currentObjectGroupName + MAT_SEP + currentMaterial.name]
= ObjectMaterialFaceGroup {
currentObjectGroupName,
currentMaterial,
currentObjectMaterialFacesPointer,
sizeOfFaceStripe
};
}
}
}