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(¶meters_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,¶meters_terrain,¶meters_cloud);
48 tab->addTab(control_terrain,"Create");
49
50 control_render=new ControlRender(¶meters_render);
51 tab->addTab(control_render,"Render");
52
53 control_save=new ControlSave(this,¶meters_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,¶meters_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