]> git.localhorst.tv Git - blobs.git/blob - src/ui/ui.cpp
balancing
[blobs.git] / src / ui / ui.cpp
1 #include "BodyPanel.hpp"
2 #include "CreaturePanel.hpp"
3 #include "RecordsPanel.hpp"
4 #include "string.hpp"
5 #include "TimePanel.hpp"
6
7 #include "Label.hpp"
8 #include "Meter.hpp"
9 #include "../app/Assets.hpp"
10 #include "../creature/Creature.hpp"
11 #include "../creature/Goal.hpp"
12 #include "../graphics/Viewport.hpp"
13 #include "../math/const.hpp"
14 #include "../world/Body.hpp"
15 #include "../world/Simulation.hpp"
16
17 #include <iomanip>
18 #include <iostream>
19 #include <sstream>
20 #include <glm/gtx/io.hpp>
21 #include <glm/gtx/transform.hpp>
22
23
24 namespace blobs {
25 namespace ui {
26
27 CreaturePanel::CreaturePanel(app::Assets &assets)
28 : assets(assets)
29 , c(nullptr)
30 , name(new Label(assets.fonts.large))
31 , parents(new Label(assets.fonts.medium))
32 , born(new Label(assets.fonts.medium))
33 , age(new Label(assets.fonts.medium))
34 , mass(new Label(assets.fonts.medium))
35 , size(new Label(assets.fonts.medium))
36 , goal(new Label(assets.fonts.medium))
37 , pos(new Label(assets.fonts.medium))
38 , tile(new Label(assets.fonts.medium))
39 , head(new Label(assets.fonts.medium))
40 , composition(new Panel)
41 , stats{nullptr}
42 , props{nullptr}
43 , panel() {
44         Label *parents_label = new Label(assets.fonts.medium);
45         parents_label->Text("Parents");
46         Label *born_label = new Label(assets.fonts.medium);
47         born_label->Text("Born");
48         Label *age_label = new Label(assets.fonts.medium);
49         age_label->Text("Age");
50         Label *mass_label = new Label(assets.fonts.medium);
51         mass_label->Text("Mass");
52         Label *size_label = new Label(assets.fonts.medium);
53         size_label->Text("Size");
54         Label *goal_label = new Label(assets.fonts.medium);
55         goal_label->Text("Goal");
56
57         Panel *info_label_panel = new Panel;
58         info_label_panel
59                 ->Direction(Panel::VERTICAL)
60                 ->Add(parents_label)
61                 ->Add(born_label)
62                 ->Add(age_label)
63                 ->Add(mass_label)
64                 ->Add(size_label)
65                 ->Add(goal_label);
66         Panel *info_value_panel = new Panel;
67         info_value_panel
68                 ->Direction(Panel::VERTICAL)
69                 ->Add(parents)
70                 ->Add(born)
71                 ->Add(age)
72                 ->Add(mass)
73                 ->Add(size)
74                 ->Add(goal);
75         Panel *info_panel = new Panel;
76         info_panel
77                 ->Direction(Panel::HORIZONTAL)
78                 ->Spacing(10.0f)
79                 ->Add(info_label_panel)
80                 ->Add(info_value_panel);
81
82         Label *pos_label = new Label(assets.fonts.medium);
83         pos_label->Text("Pos");
84         Label *tile_label = new Label(assets.fonts.medium);
85         tile_label->Text("Tile");
86         Label *head_label = new Label(assets.fonts.medium);
87         head_label->Text("Heading");
88
89         Panel *loc_label_panel = new Panel;
90         loc_label_panel
91                 ->Direction(Panel::VERTICAL)
92                 ->Add(pos_label)
93                 ->Add(tile_label)
94                 ->Add(head_label);
95         Panel *loc_value_panel = new Panel;
96         loc_value_panel
97                 ->Direction(Panel::VERTICAL)
98                 ->Add(pos)
99                 ->Add(tile)
100                 ->Add(head);
101         Panel *loc_panel = new Panel;
102         loc_panel
103                 ->Direction(Panel::HORIZONTAL)
104                 ->Spacing(10.0f)
105                 ->Add(loc_label_panel)
106                 ->Add(loc_value_panel);
107
108         Label *stat_label[7];
109         for (int i = 0; i < 7; ++i) {
110                 stat_label[i] = new Label(assets.fonts.medium);
111                 stats[i] = new Meter;
112                 stats[i]
113                         ->Size(glm::vec2(100.0f, assets.fonts.medium.Height() + assets.fonts.medium.Descent()))
114                         ->Padding(glm::vec2(1.0f))
115                         ->Border(1.0f)
116                         ->BorderColor(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
117         }
118         stat_label[0]->Text("Damage");
119         stat_label[1]->Text("Breath");
120         stat_label[2]->Text("Thirst");
121         stat_label[3]->Text("Hunger");
122         stat_label[4]->Text("Exhaustion");
123         stat_label[5]->Text("Fatigue");
124         stat_label[6]->Text("Boredom");
125
126         Panel *stat_label_panel = new Panel;
127         stat_label_panel
128                 ->Spacing(2)
129                 ->Direction(Panel::VERTICAL);
130         Panel *stat_meter_panel = new Panel;
131         stat_label[0]->Layout();
132         stats[0]->Layout();
133         stat_meter_panel
134                 ->Spacing(stat_label[0]->Size().y - stats[0]->Size().y + 2)
135                 ->Direction(Panel::VERTICAL);
136         for (int i = 0; i < 7; ++i) {
137                 stat_label_panel->Add(stat_label[i]);
138                 stat_meter_panel->Add(stats[i]);
139         }
140         Panel *stat_panel = new Panel;
141         stat_panel
142                 ->Direction(Panel::HORIZONTAL)
143                 ->Spacing(10)
144                 ->Add(stat_label_panel)
145                 ->Add(stat_meter_panel);
146
147         Label *prop_label[9];
148         for (int i = 0; i < 9; ++i) {
149                 prop_label[i] = new Label(assets.fonts.medium);
150                 props[i] = new Label(assets.fonts.medium);
151         }
152         prop_label[0]->Text("Strength");
153         prop_label[1]->Text("Stamina");
154         prop_label[2]->Text("Dexerty");
155         prop_label[3]->Text("Intelligence");
156         prop_label[4]->Text("Lifetime");
157         prop_label[5]->Text("Fertility");
158         prop_label[6]->Text("Mutability");
159         prop_label[7]->Text("Adaptability");
160         prop_label[8]->Text("Offspring mass");
161
162         Panel *prop_label_panel = new Panel;
163         prop_label_panel
164                 ->Spacing(2)
165                 ->Direction(Panel::VERTICAL);
166         Panel *prop_meter_panel = new Panel;
167         prop_meter_panel
168                 ->Spacing(2)
169                 ->Direction(Panel::VERTICAL);
170         for (int i = 0; i < 9; ++i) {
171                 prop_label_panel->Add(prop_label[i]);
172                 prop_meter_panel->Add(props[i]);
173         }
174         Panel *prop_panel = new Panel;
175         prop_panel
176                 ->Direction(Panel::HORIZONTAL)
177                 ->Spacing(10)
178                 ->Add(prop_label_panel)
179                 ->Add(prop_meter_panel);
180
181         panel
182                 .Add(name)
183                 ->Add(info_panel)
184                 ->Add(loc_panel)
185                 ->Add(composition)
186                 ->Add(stat_panel)
187                 ->Add(prop_panel)
188                 ->Padding(glm::vec2(10.0f))
189                 ->Spacing(10.0f)
190                 ->Direction(Panel::VERTICAL)
191                 ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f));
192 }
193
194 CreaturePanel::~CreaturePanel() {
195 }
196
197
198 void CreaturePanel::Show(creature::Creature &cr) {
199         c = &cr;
200         born->Text(TimeString(c->Born()));
201
202         if (c->Parents().empty()) {
203                 parents->Text("none");
204         } else {
205                 std::string parent_string;
206                 bool first = true;
207                 for (auto p : c->Parents()) {
208                         if (first) {
209                                 first = false;
210                         } else {
211                                 parent_string += " and ";
212                         }
213                         parent_string += p->Name();
214                 }
215                 parents->Text(parent_string);
216         }
217 }
218
219 void CreaturePanel::Hide() noexcept {
220         c = nullptr;
221 }
222
223 void CreaturePanel::Draw(graphics::Viewport &viewport) noexcept {
224         if (!c) return;
225
226         std::string name_str(c->Name());
227         if (c->Dead()) {
228                 name_str += " (deceased)";
229         }
230         name->Text(name_str);
231         age->Text(TimeString(c->Age()));
232         mass->Text(MassString(c->Mass()));
233         size->Text(LengthString(c->Size()));
234         if (c->Goals().empty()) {
235                 goal->Text("none");
236         } else {
237                 goal->Text(c->Goals()[0]->Describe());
238         }
239
240         pos->Text(VectorString(c->GetSituation().Position(), 2));
241         tile->Text(c->GetSituation().GetTileType().label + (
242                 c->GetSituation().OnGround()
243                         ? (c->GetSituation().Moving() ? " (moving)" : " (standing)")
244                         : (c->GetSituation().Moving() ? " (flying)" : " (hovering)")
245         ));
246         head->Text(VectorString(c->GetSituation().Heading(), 2));
247
248         const creature::Composition &comp = c->GetComposition();
249         if (comp.size() < components.size()) {
250                 components.clear();
251                 composition->Clear();
252         }
253         while (comp.size() > components.size()) {
254                 components.emplace_back(new Label(assets.fonts.medium));
255                 composition->Add(components.back());
256         }
257         {
258                 int i = 0;
259                 for (auto &cmp : comp) {
260                         components[i]->Text(
261                                 PercentageString(cmp.value / comp.TotalMass())
262                                 + " " + assets.data.resources[cmp.resource].label);
263                         ++i;
264                 }
265         }
266
267         for (int i = 0; i < 7; ++i) {
268                 stats[i]->Value(c->GetStats().stat[i].value);
269                 if (c->GetStats().stat[i].Okay()) {
270                         stats[i]->FillColor(glm::vec4(0.0f, 0.7f, 0.0f, 1.0f));
271                 } else if (c->GetStats().stat[i].Critical()) {
272                         stats[i]->FillColor(glm::vec4(0.7f, 0.0f, 0.0f, 1.0f));
273                 } else {
274                         stats[i]->FillColor(glm::vec4(0.9f, 0.4f, 0.0f, 1.0f));
275                 }
276         }
277
278         props[0]->Text(DecimalString(c->Strength(), 2) + " / " + DecimalString(c->GetProperties().Strength(), 2));
279         props[1]->Text(DecimalString(c->Stamina(), 2) + " / " + DecimalString(c->GetProperties().Stamina(), 2));
280         props[2]->Text(DecimalString(c->Dexerty(), 2) + " / " + DecimalString(c->GetProperties().Dexerty(), 2));
281         props[3]->Text(DecimalString(c->Intelligence(), 2) + " / " + DecimalString(c->GetProperties().Intelligence(), 2));
282         props[4]->Text(TimeString(c->Lifetime()));
283         props[5]->Text(PercentageString(c->Fertility()) + " / " + PercentageString(c->GetProperties().Fertility()));
284         props[6]->Text(PercentageString(c->Mutability()));
285         props[7]->Text(PercentageString(c->Adaptability()));
286         props[8]->Text(MassString(c->OffspringMass()));
287
288         const glm::vec2 margin(20.0f);
289         panel.Position(glm::vec2(viewport.Width() - margin.x - panel.Size().x, margin.y));
290         panel.Layout();
291         panel.Draw(assets, viewport);
292 }
293
294
295 BodyPanel::BodyPanel(app::Assets &assets)
296 : assets(assets)
297 , body(nullptr)
298 , name(new Label(assets.fonts.large))
299 , mass(new Label(assets.fonts.medium))
300 , radius(new Label(assets.fonts.medium))
301 , soi(new Label(assets.fonts.medium))
302 , operiod(new Label(assets.fonts.medium))
303 , rperiod(new Label(assets.fonts.medium))
304 , atm(new Label(assets.fonts.medium))
305 , sma(new Label(assets.fonts.medium))
306 , ecc(new Label(assets.fonts.medium))
307 , inc(new Label(assets.fonts.medium))
308 , asc(new Label(assets.fonts.medium))
309 , arg(new Label(assets.fonts.medium))
310 , mna(new Label(assets.fonts.medium))
311 , panel() {
312         Label *mass_label = new Label(assets.fonts.medium);
313         mass_label->Text("Mass");
314         Label *radius_label = new Label(assets.fonts.medium);
315         radius_label->Text("Radius");
316         Label *soi_label = new Label(assets.fonts.medium);
317         soi_label->Text("SOI");
318         Label *operiod_label = new Label(assets.fonts.medium);
319         operiod_label->Text("Orb. period");
320         Label *rperiod_label = new Label(assets.fonts.medium);
321         rperiod_label->Text("Rot. period");
322         Label *atm_label = new Label(assets.fonts.medium);
323         atm_label->Text("Atmosphere");
324         Label *sma_label = new Label(assets.fonts.medium);
325         sma_label->Text("SMA");
326         Label *ecc_label = new Label(assets.fonts.medium);
327         ecc_label->Text("Eccentricity");
328         Label *inc_label = new Label(assets.fonts.medium);
329         inc_label->Text("Inclination");
330         Label *asc_label = new Label(assets.fonts.medium);
331         asc_label->Text("Asc. node");
332         Label *arg_label = new Label(assets.fonts.medium);
333         arg_label->Text("Arg. Periapsis");
334         Label *mna_label = new Label(assets.fonts.medium);
335         mna_label->Text("Mean anomaly");
336         Panel *label_panel = new Panel;
337         label_panel
338                 ->Direction(Panel::VERTICAL)
339                 ->Add(mass_label)
340                 ->Add(radius_label)
341                 ->Add(soi_label)
342                 ->Add(operiod_label)
343                 ->Add(rperiod_label)
344                 ->Add(atm_label)
345                 ->Add(sma_label)
346                 ->Add(ecc_label)
347                 ->Add(inc_label)
348                 ->Add(asc_label)
349                 ->Add(arg_label)
350                 ->Add(mna_label);
351         Panel *value_panel = new Panel;
352         value_panel
353                 ->Direction(Panel::VERTICAL)
354                 ->Add(mass)
355                 ->Add(radius)
356                 ->Add(soi)
357                 ->Add(operiod)
358                 ->Add(rperiod)
359                 ->Add(atm)
360                 ->Add(sma)
361                 ->Add(ecc)
362                 ->Add(inc)
363                 ->Add(asc)
364                 ->Add(arg)
365                 ->Add(mna);
366         Panel *info_panel = new Panel;
367         info_panel
368                 ->Direction(Panel::HORIZONTAL)
369                 ->Spacing(10.0f)
370                 ->Add(label_panel)
371                 ->Add(value_panel);
372
373         panel
374                 .Add(name)
375                 ->Add(info_panel)
376                 ->Padding(glm::vec2(10.0f))
377                 ->Spacing(10.0f)
378                 ->Direction(Panel::VERTICAL)
379                 ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f));
380 }
381
382 BodyPanel::~BodyPanel() {
383 }
384
385 void BodyPanel::Show(world::Body &b) {
386         body = &b;
387         name->Text(b.Name());
388         mass->Text(MassString(b.Mass()));
389         radius->Text(LengthString(b.Radius()));
390         soi->Text(LengthString(b.SphereOfInfluence()));
391         operiod->Text(TimeString(b.OrbitalPeriod()));
392         rperiod->Text(TimeString(b.RotationalPeriod()));
393         atm->Text(b.HasAtmosphere() ? assets.data.resources[b.Atmosphere()].label : std::string("none"));
394         sma->Text(LengthString(b.GetOrbit().SemiMajorAxis()));
395         ecc->Text(DecimalString(b.GetOrbit().Eccentricity(), 3));
396         inc->Text(AngleString(b.GetOrbit().Inclination()));
397         asc->Text(AngleString(b.GetOrbit().LongitudeAscending()));
398         arg->Text(AngleString(b.GetOrbit().ArgumentPeriapsis()));
399         mna->Text(AngleString(b.GetOrbit().MeanAnomaly()));
400 }
401
402 void BodyPanel::Hide() noexcept {
403         body = nullptr;
404 }
405
406 void BodyPanel::Draw(graphics::Viewport &viewport) noexcept {
407         if (!Shown()) return;
408
409         const glm::vec2 margin(20.0f);
410         panel.Position(glm::vec2(viewport.Width() - margin.x - panel.Size().x, margin.y));
411         panel.Layout();
412         panel.Draw(assets, viewport);
413 }
414
415
416 RecordsPanel::RecordsPanel(world::Simulation &sim)
417 : sim(sim)
418 , records()
419 , holders()
420 , panel()
421 , shown(true) {
422         Label *rank_label = new Label(sim.Assets().fonts.medium);
423         rank_label->Text("Rank");
424
425         Panel *rank_panel = new Panel;
426         rank_panel
427                 ->Direction(Panel::VERTICAL)
428                 ->Add(rank_label);
429
430         for (int i = 0; i < world::Record::MAX; ++i) {
431                 rank_label = new Label(sim.Assets().fonts.medium);
432                 rank_label->Text(std::to_string(i + 1));
433                 rank_panel->Add(rank_label);
434         }
435
436         panel
437                 .Direction(Panel::HORIZONTAL)
438                 ->Padding(glm::vec2(10.0f))
439                 ->Spacing(45.0f)
440                 ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f))
441                 ->Add(rank_panel);
442
443         records.reserve(sim.Records().size() * (world::Record::MAX + 1));
444         holders.reserve(sim.Records().size() * (world::Record::MAX + 1));
445         int ri = 0;
446         for (const auto &r : sim.Records()) {
447                 Panel *by_panel = new Panel;
448                 by_panel
449                         ->Direction(Panel::VERTICAL);
450                 Panel *val_panel = new Panel;
451                 val_panel
452                         ->Direction(Panel::VERTICAL);
453                 Panel *tab_panel = new Panel;
454                 tab_panel
455                         ->Direction(Panel::HORIZONTAL)
456                         ->Spacing(10.0f)
457                         ->Add(by_panel)
458                         ->Add(val_panel);
459                 Label *rec_label = new Label(sim.Assets().fonts.medium);
460                 rec_label->Text(r.name);
461                 Panel *rec_panel = new Panel;
462                 rec_panel
463                         ->Direction(Panel::VERTICAL)
464                         ->Add(rec_label)
465                         ->Add(tab_panel);
466                 for (int i = 0; i < world::Record::MAX; ++i) {
467                         Label *val_label = new Label(sim.Assets().fonts.medium);
468                         val_panel->Add(val_label);
469                         records.push_back(val_label);
470                         Label *holder_label = new Label(sim.Assets().fonts.medium);
471                         by_panel->Add(holder_label);
472                         holders.push_back(holder_label);
473                 }
474                 Panel *group_panel = new Panel;
475                 group_panel
476                         ->Direction(Panel::HORIZONTAL)
477                         ->Spacing(10.0f)
478                         ->Add(rec_panel);
479                 panel.Add(group_panel);
480                 ++ri;
481         }
482 }
483
484 RecordsPanel::~RecordsPanel() {
485 }
486
487 void RecordsPanel::Draw(graphics::Viewport &viewport) noexcept {
488         if (!shown) return;
489
490         int ri = 0;
491         for (const auto &r : sim.Records()) {
492                 for (int i = 0; i < world::Record::MAX; ++i) {
493                         if (!r.rank[i]) {
494                                 break;
495                         }
496                         records[ri * world::Record::MAX + i]->Text(r.ValueString(i));
497                         holders[ri * world::Record::MAX + i]->Text(r.rank[i].holder->Name());
498                 }
499                 ++ri;
500         }
501
502         const glm::vec2 margin(20.0f);
503         panel.Position(glm::vec2(margin.x, margin.y));
504         panel.Layout();
505         panel.Draw(sim.Assets(), viewport);
506 }
507
508
509 TimePanel::TimePanel(world::Simulation &sim)
510 : sim(sim)
511 , body(nullptr)
512 , live(new Label(sim.Assets().fonts.medium))
513 , time(new Label(sim.Assets().fonts.medium))
514 , clock(new Label(sim.Assets().fonts.medium))
515 , panel() {
516         Label *live_label = new Label(sim.Assets().fonts.medium);
517         live_label->Text("Alive");
518         Label *time_label = new Label(sim.Assets().fonts.medium);
519         time_label->Text("Time");
520         Label *clock_label = new Label(sim.Assets().fonts.medium);
521         clock_label->Text("Clock");
522
523         Panel *label_panel = new Panel;
524         label_panel
525                 ->Direction(Panel::VERTICAL)
526                 ->Add(live_label)
527                 ->Add(time_label)
528                 ->Add(clock_label);
529
530         Panel *value_panel = new Panel;
531         value_panel
532                 ->Direction(Panel::VERTICAL)
533                 ->Add(live)
534                 ->Add(time)
535                 ->Add(clock);
536
537         panel
538                 .Direction(Panel::HORIZONTAL)
539                 ->Padding(glm::vec2(10.0f))
540                 ->Spacing(10.0f)
541                 ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f))
542                 ->Add(label_panel)
543                 ->Add(value_panel);
544 }
545
546 TimePanel::~TimePanel() {
547 }
548
549 void TimePanel::Draw(graphics::Viewport &viewport) noexcept {
550         live->Text(NumberString(sim.LiveCreatures().size()));
551         time->Text(TimeString(sim.Time()));
552         if (body) {
553                 clock->Text(TimeString(std::fmod(sim.Time(), body->DayLength())) + " / " + TimeString(body->DayLength()));
554         } else {
555                 clock->Text("no reference");
556         }
557
558         const glm::vec2 margin(20.0f);
559         panel.Position(glm::vec2(margin.x, viewport.Height() - margin.y - panel.Size().y));
560         panel.Layout();
561         panel.Draw(sim.Assets(), viewport);
562 }
563
564
565 namespace {
566 std::ostream &custom_fixed(std::ostream &out, double n, int d) {
567         double f = n;
568         int p = d;
569         while (f > 1.0 && p > 0) {
570                 --p;
571                 f *= 0.1;
572         }
573         if (p > 0) {
574                 out << std::fixed << std::setprecision(p) << n;
575         } else {
576                 out.unsetf(std::ios_base::floatfield);
577                 out << std::setprecision(d) << n;
578         }
579         return out;
580 }
581 }
582
583 std::string AngleString(double a) {
584         std::stringstream s;
585         s << std::fixed << std::setprecision(2) << std::fmod(a * 180.0 * PI_inv, 360.0) << "°";
586         return s.str();
587 }
588
589 std::string DecimalString(double n, int p) {
590         std::stringstream s;
591         s << std::fixed << std::setprecision(p) << n;
592         return s.str();
593 }
594
595 std::string LengthString(double m) {
596         std::stringstream s;
597         if (m > 1.5e9) {
598                 custom_fixed(s, m * 1.0e-9, 4) << "Gm";
599         } else if (m > 1.5e6) {
600                 custom_fixed(s, m * 1.0e-6, 4) << "Mm";
601         } else if (m > 1.5e3) {
602                 custom_fixed(s, m * 1.0e-3, 4) << "km";
603         } else if (m < 1.5e-3) {
604                 custom_fixed(s, m * 1.0e3, 4) << "mm";
605         } else if (m < 1.5) {
606                 custom_fixed(s, m * 1.0e2, 4) << "cm";
607         } else {
608                 custom_fixed(s, m, 4) << "m";
609         }
610         return s.str();
611 }
612
613 std::string MassString(double kg) {
614         std::stringstream s;
615         if (kg > 1.5e12) {
616                 custom_fixed(s, kg * 1.0e-12, 4) << "Gt";
617         } else if (kg > 1.5e9) {
618                 custom_fixed(s, kg * 1.0e-9, 4) << "Mt";
619         } else if (kg > 1.5e6) {
620                 custom_fixed(s, kg * 1.0e-6, 4) << "kt";
621         } else if (kg > 1.5e3) {
622                 custom_fixed(s, kg * 1.0e-3, 4) << "t";
623         } else if (kg < 1.0e-3) {
624                 custom_fixed(s, kg * 1.0e6, 4) << "mg";
625         } else if (kg < 1.0) {
626                 custom_fixed(s, kg * 1.0e3, 4) << "g";
627         } else {
628                 custom_fixed(s, kg, 4) << "kg";
629         }
630         return s.str();
631 }
632
633 std::string NumberString(int n) {
634         return std::to_string(n);
635 }
636
637 std::string PercentageString(double n) {
638         std::stringstream s;
639         s << std::fixed << std::setprecision(1) << (n * 100.0) << '%';
640         return s.str();
641 }
642
643 std::string TimeString(double s) {
644         int is = int(s);
645         int md = 1;
646         int sd = 1;
647         std::stringstream ss;
648         ss << std::setfill('0');
649         if (is >= 3600) {
650                 ss << (is / 3600) << "h ";
651                 is %= 3600;
652                 md = 2;
653         }
654         if (is >= 60) {
655                 ss << std::setw(md) << (is / 60) << "m ";
656                 is %= 60;
657                 sd = 2;
658         }
659         ss << std::setw(sd) << is << 's';
660         return ss.str();
661 }
662
663 std::string VectorString(const glm::dvec3 &v, int p) {
664         std::stringstream ss;
665         ss << std::fixed << std::setprecision(p)
666                 << "<" << v.x << ", " << v.y << ", " << v.z << ">";
667         return ss.str();
668 }
669
670 std::string VectorString(const glm::ivec2 &v) {
671         std::stringstream ss;
672         ss << "<" << v.x << ", " << v.y << ">";
673         return ss.str();
674 }
675
676 }
677 }