Merge branch 'master' of ssh://git.xonotic.org/netradiant
[divverent/netradiant.git] / contrib / brushexport / export.cpp
1 #include "export.h"
2 #include "debugging/debugging.h"
3 #include "ibrush.h"
4 #include "iscenegraph.h"
5 #include "iselection.h"
6 #include "stream/stringstream.h"
7 #include "stream/textfilestream.h"
8
9 #include <map>
10
11 // this is very evil, but right now there is no better way
12 #include "../../radiant/brush.h"
13
14 // for limNames
15 #define MAX_MATERIAL_NAME 20
16
17 /*
18         Abstract baseclass for modelexporters
19         the class collects all the data which then gets
20         exported through the WriteToFile method.
21 */
22 class ExportData
23 {
24 public:
25         ExportData(const std::set<std::string>& ignorelist, collapsemode mode, bool limNames, bool objs);
26         virtual ~ExportData(void);
27         
28         virtual void BeginBrush(Brush& b);
29         virtual void AddBrushFace(Face& f);
30         virtual void EndBrush(void);
31         
32         virtual bool WriteToFile(const std::string& path, collapsemode mode) const = 0;
33         
34 protected:
35
36         // a group of faces
37         class group
38         {
39         public:
40                 std::string name;
41                 std::list<const Face*> faces;
42         };
43         
44         std::list<group> groups;
45         
46 private:
47
48         // "textures/common/caulk" -> "caulk"
49         void GetShaderNameFromShaderPath(const char* path, std::string& name);
50
51         group* current; 
52         collapsemode mode;
53         const std::set<std::string>& ignorelist;
54 };
55
56 ExportData::ExportData(const std::set<std::string>& _ignorelist, collapsemode _mode, bool _limNames, bool _objs)
57         :       mode(_mode),
58                 ignorelist(_ignorelist)
59 {
60         current = 0;
61         
62         // in this mode, we need just one group
63         if(mode == COLLAPSE_ALL)
64         {
65                 groups.push_back(group());
66                 current = &groups.back();
67                 current->name = "all";
68         }
69 }
70
71 ExportData::~ExportData(void)
72 {
73         
74 }
75
76 void ExportData::BeginBrush(Brush& b)
77 {
78         // create a new group for each brush
79         if(mode == COLLAPSE_NONE)
80         {
81                 groups.push_back(group());
82                 current = &groups.back();
83                 
84                 StringOutputStream str(256);
85                 str << "Brush" << (const unsigned int)groups.size();
86                 current->name = str.c_str();
87         }
88 }
89
90 void ExportData::EndBrush(void)
91 {
92         // all faces of this brush were on the ignorelist, discard the emptygroup
93         if(mode == COLLAPSE_NONE)
94         {
95                 ASSERT_NOTNULL(current);
96                 if(current->faces.empty())
97                 {
98                         groups.pop_back();
99                         current = 0;
100                 }
101         }
102 }
103
104 void ExportData::AddBrushFace(Face& f)
105 {
106         std::string shadername;
107         GetShaderNameFromShaderPath(f.GetShader(), shadername);
108         
109         // ignore faces from ignore list
110         if(ignorelist.find(shadername) != ignorelist.end())
111                 return;
112                 
113         if(mode == COLLAPSE_BY_MATERIAL)
114         {
115                 // find a group for this material
116                 current = 0;
117                 const std::list<group>::iterator end(groups.end());
118                 for(std::list<group>::iterator it(groups.begin()); it != end; ++it)
119                 {
120                         if(it->name == shadername)
121                                 current = &(*it);
122                 }
123                 
124                 // no group found, create one
125                 if(!current)
126                 {
127                         groups.push_back(group());
128                         current = &groups.back();
129                         current->name = shadername;
130                 }
131         }
132         
133         ASSERT_NOTNULL(current);
134         
135         // add face to current group
136         current->faces.push_back(&f);
137         
138 #ifdef _DEBUG
139         globalOutputStream() << "Added Face to group " << current->name.c_str() << "\n";
140 #endif
141 }
142
143 void ExportData::GetShaderNameFromShaderPath(const char* path, std::string& name)
144 {
145         std::string tmp(path);
146
147         size_t last_slash = tmp.find_last_of("/");
148         
149         if(last_slash != std::string::npos && last_slash == (tmp.length() - 1))
150                 name = path;
151         else
152                 name = tmp.substr(last_slash + 1, tmp.length() - last_slash);
153
154 #ifdef _DEBUG
155         globalOutputStream() << "Last: " << (const unsigned int) last_slash << " " << "length: " << (const unsigned int)tmp.length() << "Name: " << name.c_str() << "\n";
156 #endif
157 }
158
159 /*
160         Exporter writing facedata as wavefront object
161 */
162 class ExportDataAsWavefront : public ExportData
163 {
164 private:
165         bool expmat;
166         bool limNames;
167         bool objs;
168
169 public:
170         ExportDataAsWavefront(const std::set<std::string>& _ignorelist, collapsemode _mode, bool _expmat, bool _limNames, bool _objs)
171                 : ExportData(_ignorelist, _mode, _limNames, _objs)
172         {
173                 expmat = _expmat;
174                 limNames = _limNames;
175                 objs = _objs;
176         }
177         
178         bool WriteToFile(const std::string& path, collapsemode mode) const;
179 };
180
181 bool ExportDataAsWavefront::WriteToFile(const std::string& path, collapsemode mode) const
182 {
183         std::string objFile = path.substr(0, path.length() -4) + ".obj";
184         std::string mtlFile = path.substr(0, path.length() -4) + ".mtl";
185
186         std::set<std::string> materials;
187
188         TextFileOutputStream out(objFile.c_str());
189         
190         if(out.failed())
191         {
192                 globalErrorStream() << "Unable to open file\n";
193                 return false;
194         }
195                 
196         out << "# Wavefront Objectfile exported with radiants brushexport plugin 3.0 by Thomas 'namespace' Nitschke, spam@codecreator.net\n\n";
197
198         if(expmat)
199         {
200                 size_t last = mtlFile.find_last_of("//");
201                 std::string mtllib = mtlFile.substr(last + 1, mtlFile.size() - last).c_str();
202                 out << "mtllib " << mtllib.c_str() << "\n";
203         }
204         
205         unsigned int vertex_count = 0;
206
207         const std::list<ExportData::group>::const_iterator gend(groups.end());
208         for(std::list<ExportData::group>::const_iterator git(groups.begin()); git != gend; ++git)
209         {
210                 typedef std::multimap<std::string, std::string> bm;
211                 bm brushMaterials;
212                 typedef std::pair<std::string, std::string> String_Pair;
213
214                 const std::list<const Face*>::const_iterator end(git->faces.end());
215                 
216                 // submesh starts here
217                 if(objs)
218                 {
219                         out << "\no ";
220                 } else {
221                         out << "\ng ";
222                 }
223                 out << git->name.c_str() << "\n";
224
225                 // material
226                 if(expmat && mode == COLLAPSE_ALL)
227                 {
228                         out << "usemtl material" << "\n\n";
229                         materials.insert("material");
230                 }
231
232                 for(std::list<const Face*>::const_iterator it(git->faces.begin()); it != end; ++it)
233                 {
234                         const Winding& w((*it)->getWinding());
235                         
236                         // vertices
237                         for(size_t i = 0; i < w.numpoints; ++i)
238                                         out << "v " << FloatFormat(w[i].vertex.x(), 1, 6) << " " << FloatFormat(w[i].vertex.z(), 1, 6) << " " << FloatFormat(w[i].vertex.y(), 1, 6) << "\n";
239                 }
240                 out << "\n";    
241                 
242                 for(std::list<const Face*>::const_iterator it(git->faces.begin()); it != end; ++it)
243                 {
244                         const Winding& w((*it)->getWinding());
245                         
246                         // texcoords
247                         for(size_t i = 0; i < w.numpoints; ++i)
248                                 out << "vt " << FloatFormat(w[i].texcoord.x(), 1, 6) << " " << FloatFormat(w[i].texcoord.y(), 1, 6) << "\n";
249                 }
250                 
251                 for(std::list<const Face*>::const_iterator it(git->faces.begin()); it != end; ++it)
252                 {
253                         const Winding& w((*it)->getWinding());
254                         
255                         // faces
256                         StringOutputStream faceLine(256);
257                         faceLine << "\nf";
258                         for(size_t i = 0; i < w.numpoints; ++i, ++vertex_count)
259                         {
260                                 faceLine << " " << vertex_count+1 << "/" << vertex_count+1;
261                         }
262
263                         if(mode != COLLAPSE_ALL)
264                         {
265                                 materials.insert((*it)->getShader().getShader());
266                                 brushMaterials.insert(String_Pair((*it)->getShader().getShader(), faceLine.c_str()));
267                         } else {
268                                 out << faceLine.c_str();
269                         }
270                 }
271
272                 if(mode != COLLAPSE_ALL)
273                 {
274                         std::string lastMat;
275                         std::string mat;
276                         std::string faces;
277
278                         for(bm::iterator iter = brushMaterials.begin(); iter != brushMaterials.end(); iter++)
279                         {
280                                 mat = (*iter).first.c_str();
281                                 faces = (*iter).second.c_str();
282
283                                 if(mat != lastMat)
284                                 {
285                                         if(limNames && mat.size() > MAX_MATERIAL_NAME)
286                                         {
287                                                 out << "\nusemtl " << mat.substr(mat.size() - MAX_MATERIAL_NAME, mat.size()).c_str();
288                                         } else {
289                                                 out << "\nusemtl " << mat.c_str();
290                                         }
291                                 }
292
293                                 out << faces.c_str();
294                                 lastMat = mat;
295                         }
296                 }
297
298                 out << "\n";
299         }
300
301         if(expmat)
302         {
303                 TextFileOutputStream outMtl(mtlFile.c_str());
304                 if(outMtl.failed())
305                 {
306                         globalErrorStream() << "Unable to open material file\n";
307                         return false;
308                 }
309
310                 outMtl << "# Wavefront material file exported with NetRadiants brushexport plugin.\n";
311                 outMtl << "# Material Count: " << (const Unsigned)materials.size() << "\n\n";
312                 for(std::set<std::string>::const_iterator it(materials.begin()); it != materials.end(); ++it)
313                 {
314                         if(limNames && it->size() > MAX_MATERIAL_NAME)
315                         {
316                                 outMtl << "newmtl " << it->substr(it->size() - MAX_MATERIAL_NAME, it->size()).c_str() << "\n";
317                         } else {
318                                 outMtl << "newmtl " << it->c_str() << "\n";
319                         }
320                 }
321         }
322         
323         return true;
324 }
325
326
327 class ForEachFace : public BrushVisitor
328 {
329 public:
330         ForEachFace(ExportData& _exporter)
331                 : exporter(_exporter)
332         {}
333         
334         void visit(Face& face) const
335         {
336                 exporter.AddBrushFace(face);
337         }
338         
339 private:
340         ExportData& exporter;
341 };
342
343 class ForEachSelected : public SelectionSystem::Visitor
344 {
345 public:
346         ForEachSelected(ExportData& _exporter)
347                 : exporter(_exporter)
348         {}
349         
350         void visit(scene::Instance& instance) const
351     {
352                 BrushInstance* bptr = InstanceTypeCast<BrushInstance>::cast(instance);
353                 if(bptr)
354                 {
355                         Brush& brush(bptr->getBrush());
356                         
357                         exporter.BeginBrush(brush);
358                                 ForEachFace face_vis(exporter);
359                                 brush.forEachFace(face_vis);
360                         exporter.EndBrush();
361                 }
362     }
363     
364 private:
365         ExportData& exporter;
366 };
367  
368 bool ExportSelection(const std::set<std::string>& ignorelist, collapsemode m, bool exmat, const std::string& path, bool limNames, bool objs)
369 {
370         ExportDataAsWavefront exporter(ignorelist, m, exmat, limNames, objs);
371         
372         ForEachSelected vis(exporter);
373         GlobalSelectionSystem().foreachSelected(vis);
374         
375         return exporter.WriteToFile(path, m);
376 }