]> git.localhorst.tv Git - blobs.git/blob - src/ui/ui.cpp
better heading implementation
[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         name->Text(c->Name());
227         age->Text(TimeString(c->Age()));
228         mass->Text(MassString(c->Mass()));
229         size->Text(LengthString(c->Size()));
230         if (c->Goals().empty()) {
231                 goal->Text("none");
232         } else {
233                 goal->Text(c->Goals()[0]->Describe());
234         }
235
236         pos->Text(VectorString(c->GetSituation().Position(), 2));
237         tile->Text(c->GetSituation().GetTileType().label + (
238                 c->GetSituation().OnGround()
239                         ? (c->GetSituation().Moving() ? " (moving)" : " (standing)")
240                         : (c->GetSituation().Moving() ? " (flying)" : " (hovering)")
241         ));
242         head->Text(VectorString(c->GetSituation().Heading(), 2));
243
244         const creature::Composition &comp = c->GetComposition();
245         if (comp.size() < components.size()) {
246                 components.clear();
247                 composition->Clear();
248         }
249         while (comp.size() > components.size()) {
250                 components.emplace_back(new Label(assets.fonts.medium));
251                 composition->Add(components.back());
252         }
253         {
254                 int i = 0;
255                 for (auto &cmp : comp) {
256                         components[i]->Text(
257                                 PercentageString(cmp.value / comp.TotalMass())
258                                 + " " + assets.data.resources[cmp.resource].label);
259                         ++i;
260                 }
261         }
262
263         for (int i = 0; i < 7; ++i) {
264                 stats[i]->Value(c->GetStats().stat[i].value);
265                 if (c->GetStats().stat[i].Okay()) {
266                         stats[i]->FillColor(glm::vec4(0.0f, 0.7f, 0.0f, 1.0f));
267                 } else if (c->GetStats().stat[i].Critical()) {
268                         stats[i]->FillColor(glm::vec4(0.7f, 0.0f, 0.0f, 1.0f));
269                 } else {
270                         stats[i]->FillColor(glm::vec4(0.9f, 0.4f, 0.0f, 1.0f));
271                 }
272         }
273
274         props[0]->Text(DecimalString(c->Strength(), 2) + " / " + DecimalString(c->GetProperties().Strength(), 2));
275         props[1]->Text(DecimalString(c->Stamina(), 2) + " / " + DecimalString(c->GetProperties().Stamina(), 2));
276         props[2]->Text(DecimalString(c->Dexerty(), 2) + " / " + DecimalString(c->GetProperties().Dexerty(), 2));
277         props[3]->Text(DecimalString(c->Intelligence(), 2) + " / " + DecimalString(c->GetProperties().Intelligence(), 2));
278         props[4]->Text(TimeString(c->Lifetime()));
279         props[5]->Text(PercentageString(c->Fertility()) + " / " + PercentageString(c->GetProperties().Fertility()));
280         props[6]->Text(PercentageString(c->Mutability()));
281         props[7]->Text(PercentageString(c->Adaptability()));
282         props[8]->Text(MassString(c->OffspringMass()));
283
284         const glm::vec2 margin(20.0f);
285         panel.Position(glm::vec2(viewport.Width() - margin.x - panel.Size().x, margin.y));
286         panel.Layout();
287         panel.Draw(assets, viewport);
288 }
289
290
291 BodyPanel::BodyPanel(app::Assets &assets)
292 : assets(assets)
293 , body(nullptr)
294 , name(new Label(assets.fonts.large))
295 , mass(new Label(assets.fonts.medium))
296 , radius(new Label(assets.fonts.medium))
297 , soi(new Label(assets.fonts.medium))
298 , operiod(new Label(assets.fonts.medium))
299 , rperiod(new Label(assets.fonts.medium))
300 , atm(new Label(assets.fonts.medium))
301 , sma(new Label(assets.fonts.medium))
302 , ecc(new Label(assets.fonts.medium))
303 , inc(new Label(assets.fonts.medium))
304 , asc(new Label(assets.fonts.medium))
305 , arg(new Label(assets.fonts.medium))
306 , mna(new Label(assets.fonts.medium))
307 , panel() {
308         Label *mass_label = new Label(assets.fonts.medium);
309         mass_label->Text("Mass");
310         Label *radius_label = new Label(assets.fonts.medium);
311         radius_label->Text("Radius");
312         Label *soi_label = new Label(assets.fonts.medium);
313         soi_label->Text("SOI");
314         Label *operiod_label = new Label(assets.fonts.medium);
315         operiod_label->Text("Orb. period");
316         Label *rperiod_label = new Label(assets.fonts.medium);
317         rperiod_label->Text("Rot. period");
318         Label *atm_label = new Label(assets.fonts.medium);
319         atm_label->Text("Atmosphere");
320         Label *sma_label = new Label(assets.fonts.medium);
321         sma_label->Text("SMA");
322         Label *ecc_label = new Label(assets.fonts.medium);
323         ecc_label->Text("Eccentricity");
324         Label *inc_label = new Label(assets.fonts.medium);
325         inc_label->Text("Inclination");
326         Label *asc_label = new Label(assets.fonts.medium);
327         asc_label->Text("Asc. node");
328         Label *arg_label = new Label(assets.fonts.medium);
329         arg_label->Text("Arg. Periapsis");
330         Label *mna_label = new Label(assets.fonts.medium);
331         mna_label->Text("Mean anomaly");
332         Panel *label_panel = new Panel;
333         label_panel
334                 ->Direction(Panel::VERTICAL)
335                 ->Add(mass_label)
336                 ->Add(radius_label)
337                 ->Add(soi_label)
338                 ->Add(operiod_label)
339                 ->Add(rperiod_label)
340                 ->Add(atm_label)
341                 ->Add(sma_label)
342                 ->Add(ecc_label)
343                 ->Add(inc_label)
344                 ->Add(asc_label)
345                 ->Add(arg_label)
346                 ->Add(mna_label);
347         Panel *value_panel = new Panel;
348         value_panel
349                 ->Direction(Panel::VERTICAL)
350                 ->Add(mass)
351                 ->Add(radius)
352                 ->Add(soi)
353                 ->Add(operiod)
354                 ->Add(rperiod)
355                 ->Add(atm)
356                 ->Add(sma)
357                 ->Add(ecc)
358                 ->Add(inc)
359                 ->Add(asc)
360                 ->Add(arg)
361                 ->Add(mna);
362         Panel *info_panel = new Panel;
363         info_panel
364                 ->Direction(Panel::HORIZONTAL)
365                 ->Spacing(10.0f)
366                 ->Add(label_panel)
367                 ->Add(value_panel);
368
369         panel
370                 .Add(name)
371                 ->Add(info_panel)
372                 ->Padding(glm::vec2(10.0f))
373                 ->Spacing(10.0f)
374                 ->Direction(Panel::VERTICAL)
375                 ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f));
376 }
377
378 BodyPanel::~BodyPanel() {
379 }
380
381 void BodyPanel::Show(world::Body &b) {
382         body = &b;
383         name->Text(b.Name());
384         mass->Text(MassString(b.Mass()));
385         radius->Text(LengthString(b.Radius()));
386         soi->Text(LengthString(b.SphereOfInfluence()));
387         operiod->Text(TimeString(b.OrbitalPeriod()));
388         rperiod->Text(TimeString(b.RotationalPeriod()));
389         atm->Text(b.HasAtmosphere() ? assets.data.resources[b.Atmosphere()].label : std::string("none"));
390         sma->Text(LengthString(b.GetOrbit().SemiMajorAxis()));
391         ecc->Text(DecimalString(b.GetOrbit().Eccentricity(), 3));
392         inc->Text(AngleString(b.GetOrbit().Inclination()));
393         asc->Text(AngleString(b.GetOrbit().LongitudeAscending()));
394         arg->Text(AngleString(b.GetOrbit().ArgumentPeriapsis()));
395         mna->Text(AngleString(b.GetOrbit().MeanAnomaly()));
396 }
397
398 void BodyPanel::Hide() noexcept {
399         body = nullptr;
400 }
401
402 void BodyPanel::Draw(graphics::Viewport &viewport) noexcept {
403         if (!Shown()) return;
404
405         const glm::vec2 margin(20.0f);
406         panel.Position(glm::vec2(viewport.Width() - margin.x - panel.Size().x, margin.y));
407         panel.Layout();
408         panel.Draw(assets, viewport);
409 }
410
411
412 RecordsPanel::RecordsPanel(world::Simulation &sim)
413 : sim(sim)
414 , records()
415 , holders()
416 , panel()
417 , shown(true) {
418         Label *rank_label = new Label(sim.Assets().fonts.medium);
419         rank_label->Text("Rank");
420
421         Panel *rank_panel = new Panel;
422         rank_panel
423                 ->Direction(Panel::VERTICAL)
424                 ->Add(rank_label);
425
426         for (int i = 0; i < world::Record::MAX; ++i) {
427                 rank_label = new Label(sim.Assets().fonts.medium);
428                 rank_label->Text(std::to_string(i + 1));
429                 rank_panel->Add(rank_label);
430         }
431
432         panel
433                 .Direction(Panel::HORIZONTAL)
434                 ->Padding(glm::vec2(10.0f))
435                 ->Spacing(45.0f)
436                 ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f))
437                 ->Add(rank_panel);
438
439         records.reserve(sim.Records().size() * (world::Record::MAX + 1));
440         holders.reserve(sim.Records().size() * (world::Record::MAX + 1));
441         int ri = 0;
442         for (const auto &r : sim.Records()) {
443                 Panel *by_panel = new Panel;
444                 by_panel
445                         ->Direction(Panel::VERTICAL);
446                 Panel *val_panel = new Panel;
447                 val_panel
448                         ->Direction(Panel::VERTICAL);
449                 Panel *tab_panel = new Panel;
450                 tab_panel
451                         ->Direction(Panel::HORIZONTAL)
452                         ->Spacing(10.0f)
453                         ->Add(by_panel)
454                         ->Add(val_panel);
455                 Label *rec_label = new Label(sim.Assets().fonts.medium);
456                 rec_label->Text(r.name);
457                 Panel *rec_panel = new Panel;
458                 rec_panel
459                         ->Direction(Panel::VERTICAL)
460                         ->Add(rec_label)
461                         ->Add(tab_panel);
462                 for (int i = 0; i < world::Record::MAX; ++i) {
463                         Label *val_label = new Label(sim.Assets().fonts.medium);
464                         val_panel->Add(val_label);
465                         records.push_back(val_label);
466                         Label *holder_label = new Label(sim.Assets().fonts.medium);
467                         by_panel->Add(holder_label);
468                         holders.push_back(holder_label);
469                 }
470                 Panel *group_panel = new Panel;
471                 group_panel
472                         ->Direction(Panel::HORIZONTAL)
473                         ->Spacing(10.0f)
474                         ->Add(rec_panel);
475                 panel.Add(group_panel);
476                 ++ri;
477         }
478 }
479
480 RecordsPanel::~RecordsPanel() {
481 }
482
483 void RecordsPanel::Draw(graphics::Viewport &viewport) noexcept {
484         if (!shown) return;
485
486         int ri = 0;
487         for (const auto &r : sim.Records()) {
488                 for (int i = 0; i < world::Record::MAX; ++i) {
489                         if (!r.rank[i]) {
490                                 break;
491                         }
492                         records[ri * world::Record::MAX + i]->Text(r.ValueString(i));
493                         holders[ri * world::Record::MAX + i]->Text(r.rank[i].holder->Name());
494                 }
495                 ++ri;
496         }
497
498         const glm::vec2 margin(20.0f);
499         panel.Position(glm::vec2(margin.x, margin.y));
500         panel.Layout();
501         panel.Draw(sim.Assets(), viewport);
502 }
503
504
505 TimePanel::TimePanel(world::Simulation &sim)
506 : sim(sim)
507 , body(nullptr)
508 , live(new Label(sim.Assets().fonts.medium))
509 , time(new Label(sim.Assets().fonts.medium))
510 , clock(new Label(sim.Assets().fonts.medium))
511 , panel() {
512         Label *live_label = new Label(sim.Assets().fonts.medium);
513         live_label->Text("Alive");
514         Label *time_label = new Label(sim.Assets().fonts.medium);
515         time_label->Text("Time");
516         Label *clock_label = new Label(sim.Assets().fonts.medium);
517         clock_label->Text("Clock");
518
519         Panel *label_panel = new Panel;
520         label_panel
521                 ->Direction(Panel::VERTICAL)
522                 ->Add(live_label)
523                 ->Add(time_label)
524                 ->Add(clock_label);
525
526         Panel *value_panel = new Panel;
527         value_panel
528                 ->Direction(Panel::VERTICAL)
529                 ->Add(live)
530                 ->Add(time)
531                 ->Add(clock);
532
533         panel
534                 .Direction(Panel::HORIZONTAL)
535                 ->Padding(glm::vec2(10.0f))
536                 ->Spacing(10.0f)
537                 ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f))
538                 ->Add(label_panel)
539                 ->Add(value_panel);
540 }
541
542 TimePanel::~TimePanel() {
543 }
544
545 void TimePanel::Draw(graphics::Viewport &viewport) noexcept {
546         live->Text(NumberString(sim.LiveCreatures().size()));
547         time->Text(TimeString(sim.Time()));
548         if (body) {
549                 clock->Text(TimeString(std::fmod(sim.Time(), body->RotationalPeriod())));
550         } else {
551                 clock->Text("no reference");
552         }
553
554         const glm::vec2 margin(20.0f);
555         panel.Position(glm::vec2(margin.x, viewport.Height() - margin.y - panel.Size().y));
556         panel.Layout();
557         panel.Draw(sim.Assets(), viewport);
558 }
559
560
561 namespace {
562 std::ostream &custom_fixed(std::ostream &out, double n, int d) {
563         double f = n;
564         int p = d;
565         while (f > 1.0 && p > 0) {
566                 --p;
567                 f *= 0.1;
568         }
569         if (p > 0) {
570                 out << std::fixed << std::setprecision(p) << n;
571         } else {
572                 out.unsetf(std::ios_base::floatfield);
573                 out << std::setprecision(d) << n;
574         }
575         return out;
576 }
577 }
578
579 std::string AngleString(double a) {
580         std::stringstream s;
581         s << std::fixed << std::setprecision(2) << std::fmod(a * 180.0 * PI_inv, 360.0) << "°";
582         return s.str();
583 }
584
585 std::string DecimalString(double n, int p) {
586         std::stringstream s;
587         s << std::fixed << std::setprecision(p) << n;
588         return s.str();
589 }
590
591 std::string LengthString(double m) {
592         std::stringstream s;
593         if (m > 1.5e9) {
594                 custom_fixed(s, m * 1.0e-9, 4) << "Gm";
595         } else if (m > 1.5e6) {
596                 custom_fixed(s, m * 1.0e-6, 4) << "Mm";
597         } else if (m > 1.5e3) {
598                 custom_fixed(s, m * 1.0e-3, 4) << "km";
599         } else if (m < 1.5e-3) {
600                 custom_fixed(s, m * 1.0e3, 4) << "mm";
601         } else if (m < 1.5) {
602                 custom_fixed(s, m * 1.0e2, 4) << "cm";
603         } else {
604                 custom_fixed(s, m, 4) << "m";
605         }
606         return s.str();
607 }
608
609 std::string MassString(double kg) {
610         std::stringstream s;
611         if (kg > 1.5e12) {
612                 custom_fixed(s, kg * 1.0e-12, 4) << "Gt";
613         } else if (kg > 1.5e9) {
614                 custom_fixed(s, kg * 1.0e-9, 4) << "Mt";
615         } else if (kg > 1.5e6) {
616                 custom_fixed(s, kg * 1.0e-6, 4) << "kt";
617         } else if (kg > 1.5e3) {
618                 custom_fixed(s, kg * 1.0e-3, 4) << "t";
619         } else if (kg < 1.0e-3) {
620                 custom_fixed(s, kg * 1.0e6, 4) << "mg";
621         } else if (kg < 1.0) {
622                 custom_fixed(s, kg * 1.0e3, 4) << "g";
623         } else {
624                 custom_fixed(s, kg, 4) << "kg";
625         }
626         return s.str();
627 }
628
629 std::string NumberString(int n) {
630         return std::to_string(n);
631 }
632
633 std::string PercentageString(double n) {
634         std::stringstream s;
635         s << std::fixed << std::setprecision(1) << (n * 100.0) << '%';
636         return s.str();
637 }
638
639 std::string TimeString(double s) {
640         int is = int(s);
641         int md = 1;
642         int sd = 1;
643         std::stringstream ss;
644         ss << std::setfill('0');
645         if (is >= 3600) {
646                 ss << (is / 3600) << "h ";
647                 is %= 3600;
648                 md = 2;
649         }
650         if (is >= 60) {
651                 ss << std::setw(md) << (is / 60) << "m ";
652                 is %= 60;
653                 sd = 2;
654         }
655         ss << std::setw(sd) << is << 's';
656         return ss.str();
657 }
658
659 std::string VectorString(const glm::dvec3 &v, int p) {
660         std::stringstream ss;
661         ss << std::fixed << std::setprecision(p)
662                 << "<" << v.x << ", " << v.y << ", " << v.z << ">";
663         return ss.str();
664 }
665
666 std::string VectorString(const glm::ivec2 &v) {
667         std::stringstream ss;
668         ss << "<" << v.x << ", " << v.y << ">";
669         return ss.str();
670 }
671
672 }
673 }