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