1 /**************************************************************************/
2 /*  Copyright 2009 Tim Day                                                */
3 /*                                                                        */
4 /*  This file is part of Fracplanet                                       */
5 /*                                                                        */
6 /*  Fracplanet is free software: you can redistribute it and/or modify    */
7 /*  it under the terms of the GNU General Public License as published by  */
8 /*  the Free Software Foundation, either version 3 of the License, or     */
9 /*  (at your option) any later version.                                   */
10 /*                                                                        */
11 /*  Fracplanet is distributed in the hope that it will be useful,         */
12 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of        */
13 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         */
14 /*  GNU General Public License for more details.                          */
15 /*                                                                        */
16 /*  You should have received a copy of the GNU General Public License     */
17 /*  along with Fracplanet.  If not, see <http://www.gnu.org/licenses/>.   */
18 /**************************************************************************/
19 
20 #include "fracplanet_main.h"
21 
22 #include <boost/program_options/errors.hpp>
23 #include <boost/program_options/options_description.hpp>
24 #include <boost/program_options/parsers.hpp>
25 #include <boost/program_options/variables_map.hpp>
26 
27 #include "image.h"
28 
FracplanetMain(QWidget * parent,QApplication * app,const boost::program_options::variables_map & opts,bool verbose)29 FracplanetMain::FracplanetMain(QWidget* parent,QApplication* app,const boost::program_options::variables_map& opts,bool verbose)
30   :QWidget(parent)
31   ,_verbose(verbose)
32   ,application(app)
33   ,mesh_terrain(0)
34   ,mesh_cloud(0)
35   ,parameters_terrain()
36   ,parameters_cloud()
37   ,parameters_render(opts)
38   ,parameters_save(&parameters_render)
39   ,last_step(0)
40   ,progress_was_stalled(false)
41 {
42   setLayout(new QVBoxLayout);
43 
44   tab=new QTabWidget();
45   layout()->addWidget(tab);
46 
47   control_terrain=new ControlTerrain(this,&parameters_terrain,&parameters_cloud);
48   tab->addTab(control_terrain,"Create");
49 
50   control_render=new ControlRender(&parameters_render);
51   tab->addTab(control_render,"Render");
52 
53   control_save=new ControlSave(this,&parameters_save);
54   tab->addTab(control_save,"Save");
55 
56   control_about=new ControlAbout(application);
57   tab->addTab(control_about,"About");
58 }
59 
~FracplanetMain()60 FracplanetMain::~FracplanetMain()
61 {}
62 
progress_start(uint target,const std::string & info)63 void FracplanetMain::progress_start(uint target,const std::string& info)
64 {
65   if (!progress_dialog.get())
66     {
67       progress_dialog=std::unique_ptr<QProgressDialog>(new QProgressDialog("Progress","Cancel",0,100,this));
68       progress_dialog->setWindowModality(Qt::WindowModal);
69 
70       progress_dialog->setCancelButton(0);  // Cancel not supported
71       progress_dialog->setAutoClose(false); // Avoid it flashing on and off
72       progress_dialog->setMinimumDuration(0);
73     }
74 
75   progress_was_stalled=false;
76   progress_info=info;
77   progress_dialog->reset();
78   progress_dialog->setMaximum(target+1);   // Not sure why, but  +1 seems to avoid the progress bar dropping back to the start on completion
79   progress_dialog->setLabelText(progress_info.c_str());
80   progress_dialog->show();
81 
82   last_step=static_cast<uint>(-1);
83 
84   QApplication::setOverrideCursor(Qt::WaitCursor);
85 
86   application->processEvents();
87 }
88 
progress_stall(const std::string & reason)89 void FracplanetMain::progress_stall(const std::string& reason)
90 {
91   progress_was_stalled=true;
92   progress_dialog->setLabelText(reason.c_str());
93   application->processEvents();
94 }
95 
progress_step(uint step)96 void FracplanetMain::progress_step(uint step)
97 {
98   if (progress_was_stalled)
99     {
100       progress_dialog->setLabelText(progress_info.c_str());
101       progress_was_stalled=false;
102       application->processEvents();
103     }
104 
105   // We might be called lots of times with the same step.
106   // Don't know if Qt handles this efficiently so check for it ourselves.
107   if (step!=last_step)
108     {
109       progress_dialog->setValue(step);
110       last_step=step;
111       application->processEvents();
112     }
113 
114   // TODO: Probably better to base call to processEvents() on time since we last called it.
115   // (but certainly calling it every step is a bad idea: really slows app down)
116 }
117 
progress_complete(const std::string & info)118 void FracplanetMain::progress_complete(const std::string& info)
119 {
120   progress_dialog->setLabelText(info.c_str());
121 
122   last_step=static_cast<uint>(-1);
123 
124   QApplication::restoreOverrideCursor();
125 
126   application->processEvents();
127 }
128 
regenerate()129 void FracplanetMain::regenerate()   //! \todo Should be able to retain ground or clouds
130 {
131   const bool first_viewer=!viewer;
132 
133   if (viewer)
134     {
135       viewer->hide();
136       viewer.reset();
137     }
138 
139   meshes.clear();
140   mesh_terrain.reset();
141   mesh_cloud.reset();
142 
143   //! \todo Recreating viewer every time seems like overkill, but Ubuntu (in VM) doesn't seem to like it otherwise.
144   viewer.reset(new TriangleMeshViewer(this,&parameters_render,std::vector<const TriangleMesh*>(),_verbose));
145 
146   // Tweak viewer appearance so controls not too dominant
147   QFont viewer_font;
148   viewer_font.setPointSize(viewer_font.pointSize()-2);
149   viewer->setFont(viewer_font);
150   viewer->layout()->setSpacing(2);
151   viewer->layout()->setContentsMargins(2,2,2,2);
152 
153   const clock_t t0=clock();
154 
155   // There are some issues with type here:
156   // We need to keep hold of a pointer to TriangleMeshTerrain so we can call its write_povray method
157   // but the triangle viewer needs the TriangleMesh.
158   // So we end up with code like this to avoid a premature downcast.
159   switch (parameters_terrain.object_type)
160     {
161     case ParametersObject::ObjectTypePlanet:
162       {
163     std::unique_ptr<TriangleMeshTerrainPlanet> it(new TriangleMeshTerrainPlanet(parameters_terrain,this));
164     meshes.push_back(it.get());
165     mesh_terrain.reset(it.release());
166     break;
167       }
168     default:
169       {
170     std::unique_ptr<TriangleMeshTerrainFlat> it(new TriangleMeshTerrainFlat(parameters_terrain,this));
171     meshes.push_back(it.get());
172     mesh_terrain.reset(it.release());
173     break;
174       }
175     }
176 
177   if (parameters_cloud.enabled)
178     {
179       switch (parameters_cloud.object_type)
180     {
181     case ParametersObject::ObjectTypePlanet:
182       {
183         std::unique_ptr<TriangleMeshCloudPlanet> it(new TriangleMeshCloudPlanet(parameters_cloud,this));
184         meshes.push_back(it.get());
185         mesh_cloud.reset(it.release());
186         break;
187       }
188     default:
189       {
190         std::unique_ptr<TriangleMeshCloudFlat> it(new TriangleMeshCloudFlat(parameters_cloud,this));
191         meshes.push_back(it.get());
192         mesh_cloud.reset(it.release());
193         break;
194       }
195     }
196     }
197 
198   const clock_t t1=clock();
199 
200   progress_dialog.reset(0);
201 
202   if (_verbose)
203     std::cerr << "Mesh build time was " << (t1-t0)/static_cast<double>(CLOCKS_PER_SEC) << "s" << std::endl;
204 
205   viewer->set_mesh(meshes);
206   viewer->showNormal();  // showNormal() needed to restore from minimised
207   viewer->raise();
208 
209   // Only display this first time viewer is created
210   if (_verbose && first_viewer)
211     {
212       std::cerr << "GL info:" << std::endl;
213       std::cerr << "  Vendor   : " << glGetString(GL_VENDOR) << std::endl;
214       std::cerr << "  Renderer : " << glGetString(GL_RENDERER) << std::endl;
215       std::cerr << "  Version  : " << glGetString(GL_VERSION) << std::endl;
216 
217       GLint max_elements_vertices;
218       GLint max_elements_indices;
219       glGetIntegerv(GL_MAX_ELEMENTS_VERTICES,&max_elements_vertices);
220       glGetIntegerv(GL_MAX_ELEMENTS_INDICES,&max_elements_indices);
221       std::cerr << "  GL_MAX_ELEMENTS_VERTICES : " << max_elements_vertices << std::endl;
222       std::cerr << "  GL_MAX_ELEMENTS_INDICES : " << max_elements_indices << std::endl;
223 
224       //std::cerr << "  GL Extensions are : \"" << glGetString(GL_EXTENSIONS) << "\"" << std::endl;
225     }
226 }
227 
save_pov()228 void FracplanetMain::save_pov()
229 {
230   const QString selected_filename=QFileDialog::getSaveFileName
231     (
232      this,
233      "POV-Ray",
234      ".",
235      "(*.pov *.inc)"
236      );
237   if (selected_filename.isEmpty())
238     {
239       QMessageBox::critical(this,"Fracplanet","No file specified\nNothing saved");
240     }
241   else if (!(selected_filename.toUpper().endsWith(".POV") || selected_filename.toUpper().endsWith(".INC")))
242     {
243       QMessageBox::critical(this,"Fracplanet","File selected must have .pov or .inc suffix.");
244     }
245   else
246     {
247       viewer->hide();
248 
249       const std::string filename_base(selected_filename.left(selected_filename.length()-4).toLocal8Bit());
250       const std::string filename_pov=filename_base+".pov";
251       const std::string filename_inc=filename_base+".inc";
252 
253       const size_t last_separator=filename_inc.rfind('/');
254       const std::string filename_inc_relative_to_pov=
255     "./"
256     +(
257       last_separator==std::string::npos
258       ?
259       filename_inc
260       :
261       filename_inc.substr(last_separator+1)
262       );
263 
264       std::ofstream out_pov(filename_pov.c_str());
265       std::ofstream out_inc(filename_inc.c_str());
266 
267       // Boilerplate for renderer
268       out_pov << "camera {perspective location <0,1,-4.5> look_at <0,0,0> angle 45}\n";
269       out_pov << "light_source {<100,100,-100> color rgb <1.0,1.0,1.0>}\n";
270       out_pov << "#include \""+filename_inc_relative_to_pov+"\"\n";
271 
272       mesh_terrain->write_povray(out_inc,parameters_save,parameters_terrain);
273       if (mesh_cloud) mesh_cloud->write_povray(out_inc,parameters_save,parameters_cloud);
274 
275       out_pov.close();
276       out_inc.close();
277 
278       const bool ok=(out_pov && out_inc);
279 
280       progress_dialog.reset(0);
281 
282       viewer->showNormal();
283       viewer->raise();
284 
285       if (!ok)
286     {
287       QMessageBox::critical(this,"Fracplanet","Errors ocurred while the files were being written.");
288     }
289     }
290 }
291 
save_blender()292 void FracplanetMain::save_blender()
293   {
294     const QString selected_filename=QFileDialog::getSaveFileName
295       (
296        this,
297        "Blender",
298        ".",
299        "(*.py)"
300        );
301     if (selected_filename.isEmpty())
302       {
303         QMessageBox::critical(this,"Fracplanet","No file specified\nNothing saved");
304       }
305     else
306       {
307         viewer->hide();
308 
309         const std::string filename(selected_filename.toLocal8Bit());
310         std::ofstream out(filename.c_str());
311 
312         // Boilerplate
313         out <<
314             "#+\n"
315             "# Instructions for loading this model into Blender:\n"
316             "#\n"
317             "# Open a new Blender document. Get rid of the default cube, and any other\n"
318             "# extraneous stuff you don't want. Bring up a Text Editor window.\n"
319             "# Click the Open button. Select this .py file to load it into\n"
320             "# a new text block. Execute the text block with ALT-P. The recreated model\n"
321             "# should now be visible in the 3D view. You can now delete the text.\n"
322             "# block containing this script from the document. Save the document.\n"
323             "#-\n"
324             "\n";
325         out <<
326             "import bpy\n"
327             "import bmesh\n"
328             "from mathutils import \\\n"
329             "    Color\n"
330             "\n"
331             "bpy.ops.object.add(type = \"MESH\")\n"
332             "the_mesh_obj = bpy.context.active_object\n"
333             "the_mesh_obj.name = \"fracplanet\"\n"
334             "the_mesh = the_mesh_obj.data\n"
335             "the_mesh.name = \"fracplanet\"\n"
336             "the_bmesh = bmesh.new()\n"
337             "color_layer = the_bmesh.loops.layers.color.new(\"Col\") # same name as used by Blender\n"
338             "\n"
339             "def v(x, y, z) :\n"
340             "  # adds a new vertex to the mesh.\n"
341             "    the_bmesh.verts.new((x, y, z))\n"
342             "#end v\n"
343             "\n";
344         out <<
345           "def f(material, v0, v1, v2, c0, c1, c2) :\n"
346           "  # adds a new face and associated vertex colours to the mesh.\n"
347           "    the_bmesh.verts.index_update()\n"
348           "    tface = the_bmesh.faces.new((the_bmesh.verts[v0], the_bmesh.verts[v1], the_bmesh.verts[v2]))\n"
349           "    tface.smooth = True\n"
350           "    tface.material_index = material\n"
351           "    colors = {v0 : Color(tuple(c / 255.0 for c in c0[:3])), v1 : Color(tuple(c / 255.0 for c in c1[:3])), v2 : Color(tuple(c / 255.0 for c in c2[:3]))}\n"
352                  /* unfortunately, I can't do anything with alpha */
353           "    for l in tface.loops :\n"
354           "        l[color_layer] = colors[l.vert.index]\n"
355           "    #end for\n"
356           "#end f\n\n";
357 
358         mesh_terrain->write_blender(out,parameters_save,parameters_terrain,"fracplanet");
359         if (mesh_cloud) mesh_cloud->write_blender(out,parameters_save,parameters_cloud,"fracplanet");
360 
361         out <<
362             "the_bmesh.normal_update()\n"
363             "the_bmesh.to_mesh(the_mesh)\n"
364             "the_mesh.update()\n"
365             "bpy.context.scene.update()\n";
366 
367         out.close();
368 
369         progress_dialog.reset(0);
370 
371         viewer->showNormal();
372         viewer->raise();
373 
374         if (!out)
375       {
376         QMessageBox::critical(this,"Fracplanet","Errors ocurred while the files were being written.");
377       }
378       }
379   } /*FracplanetMain::save_blender*/
380 
save_texture()381 void FracplanetMain::save_texture()
382 {
383   const uint height=parameters_save.texture_height;
384   const uint width=height*mesh_terrain->geometry().scan_convert_image_aspect_ratio();
385 
386   const QString selected_filename=QFileDialog::getSaveFileName
387     (
388      this,
389      "Texture",
390      ".",
391      "(*.ppm)"
392      );
393   if (selected_filename.isEmpty())
394     {
395       QMessageBox::critical(this,"Fracplanet","No file specified\nNothing saved");
396     }
397   else if (!(selected_filename.toUpper().endsWith(".PPM")))
398     {
399       QMessageBox::critical(this,"Fracplanet","File selected must have .ppm suffix.");
400     }
401   else
402     {
403       const std::string filename(selected_filename.toLocal8Bit());
404       const std::string filename_base(selected_filename.left(selected_filename.length()-4).toLocal8Bit());
405 
406       viewer->hide();
407 
408       bool ok=true;
409       {
410     boost::scoped_ptr<Image<ByteRGBA> > terrain_image(new Image<ByteRGBA>(width,height));
411     terrain_image->fill(ByteRGBA(0,0,0,0));
412     boost::scoped_ptr<Image<ushort> > terrain_dem(new Image<ushort>(width,height));
413     terrain_dem->fill(0);
414 
415     boost::scoped_ptr<Image<ByteRGBA> > terrain_normals(new Image<ByteRGBA>(width,height));
416     terrain_normals->fill(ByteRGBA(128,128,128,0));
417 
418     mesh_terrain->render_texture
419       (
420        *terrain_image,
421        terrain_dem.get(),
422        terrain_normals.get(),
423        parameters_save.texture_shaded,
424        parameters_render.ambient,
425        parameters_render.illumination_direction()
426        );
427 
428     if (!terrain_image->write_ppmfile(filename,this)) ok=false;
429 
430     if (ok && terrain_dem)
431       {
432         if (!terrain_dem->write_pgmfile(filename_base+"_dem.pgm",this)) ok=false;
433       }
434 
435     if (ok && terrain_normals)
436       {
437         if (!terrain_normals->write_ppmfile(filename_base+"_norm.ppm",this)) ok=false;
438       }
439       }
440 
441       if (ok && mesh_cloud)
442     {
443       QMessageBox::warning(this,"Fracplanet","Texture save of cloud mesh not currently supported");
444       //boost::scoped_ptr<Image<uchar> > cloud_image(new Image<uchar>(width,height));
445       //mesh_cloud->render_texture(*cloud_image);
446       //if (!cloud_image->write_pgmfile(filename_base+"_cloud.png",this)) ok=false;
447     }
448 
449       progress_dialog.reset(0);
450 
451       viewer->showNormal();
452       viewer->raise();
453 
454       if (!ok)
455     {
456       QMessageBox::critical(this,"Fracplanet","Errors ocurred while the files were being written.");
457     }
458     }
459 }
460