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