]> git.localhorst.tv Git - blobs.git/blob - src/app/app.cpp
8cd49f3e97b5488b7b59901ce94c51fe157ed2bf
[blobs.git] / src / app / app.cpp
1 #include "Application.hpp"
2 #include "Assets.hpp"
3 #include "State.hpp"
4
5 #include "init.hpp"
6 #include "../graphics/Viewport.hpp"
7 #include "../io/Token.hpp"
8 #include "../io/TokenStreamReader.hpp"
9 #include "../world/Planet.hpp"
10 #include "../world/Simulation.hpp"
11 #include "../world/Sun.hpp"
12
13 #include <fstream>
14 #include <SDL.h>
15 #include <SDL_image.h>
16
17 using std::string;
18
19
20 namespace blobs {
21 namespace app {
22
23 Application::Application(Window &win, graphics::Viewport &vp)
24 : window(win)
25 , viewport(vp)
26 , states() {
27 }
28
29 Application::~Application() {
30 }
31
32
33 void Application::PushState(State *s) {
34         s->app = this;
35         if (!states.empty()) {
36                 states.top()->OnPause();
37         }
38         states.emplace(s);
39         ++s->ref_count;
40         if (s->ref_count == 1) {
41                 s->OnEnter();
42                 s->OnResize(viewport.Width(), viewport.Height());
43         }
44         s->OnResume();
45 }
46
47 State *Application::PopState() {
48         State *s = states.top();
49         states.pop();
50         s->OnPause();
51         s->OnExit();
52         if (!states.empty()) {
53                 states.top()->OnResume();
54         }
55         return s;
56 }
57
58 State *Application::SwitchState(State *s_new) {
59         s_new->app = this;
60         State *s_old = states.top();
61         states.top() = s_new;
62         --s_old->ref_count;
63         ++s_new->ref_count;
64         s_old->OnPause();
65         if (s_old->ref_count == 0) {
66                 s_old->OnExit();
67         }
68         if (s_new->ref_count == 1) {
69                 s_new->OnEnter();
70                 s_new->OnResize(viewport.Width(), viewport.Height());
71         }
72         s_new->OnResume();
73         return s_old;
74 }
75
76 State &Application::GetState() {
77         return *states.top();
78 }
79
80 bool Application::HasState() const noexcept {
81         return !states.empty();
82 }
83
84
85 void Application::Run() {
86         Uint32 last = SDL_GetTicks();
87         while (HasState()) {
88                 Uint32 now = SDL_GetTicks();
89                 int delta = now - last;
90                 Loop(delta);
91                 last = now;
92         }
93 }
94
95 void Application::Loop(int dt) {
96         HandleEvents();
97         if (!HasState()) return;
98         GetState().Update(dt);
99         if (!HasState()) return;
100         viewport.Clear();
101         GetState().Render(viewport);
102         window.Flip();
103 }
104
105 void Application::HandleEvents() {
106         SDL_Event event;
107         while (HasState() && SDL_PollEvent(&event)) {
108                 if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
109                         viewport.Resize(event.window.data1, event.window.data2);
110                 }
111                 GetState().Handle(event);
112         }
113 }
114
115 void State::Handle(const SDL_Event &event) {
116         switch (event.type) {
117                 case SDL_KEYDOWN:
118                         OnKeyDown(event.key);
119                         break;
120                 case SDL_KEYUP:
121                         OnKeyUp(event.key);
122                         break;
123                 case SDL_MOUSEBUTTONDOWN:
124                         OnMouseDown(event.button);
125                         break;
126                 case SDL_MOUSEBUTTONUP:
127                         OnMouseUp(event.button);
128                         break;
129                 case SDL_MOUSEMOTION:
130                         OnMouseMotion(event.motion);
131                         break;
132                 case SDL_MOUSEWHEEL:
133                         OnMouseWheel(event.wheel);
134                         break;
135                 case SDL_QUIT:
136                         OnQuit();
137                         break;
138                 case SDL_WINDOWEVENT:
139                         Handle(event.window);
140                         break;
141                 default:
142                         // ignore
143                         break;
144         }
145 }
146
147 void State::Handle(const SDL_WindowEvent &event) {
148         switch (event.event) {
149                 case SDL_WINDOWEVENT_FOCUS_GAINED:
150                         OnFocus();
151                         break;
152                 case SDL_WINDOWEVENT_FOCUS_LOST:
153                         OnBlur();
154                         break;
155                 case SDL_WINDOWEVENT_RESIZED:
156                         OnResize(event.data1, event.data2);
157                         break;
158                 default:
159                         break;
160         }
161 }
162
163 void State::Update(int dt) {
164         OnUpdate(dt);
165 }
166
167 void State::Render(graphics::Viewport &viewport) {
168         OnRender(viewport);
169 }
170
171 void State::OnQuit() {
172         while (App().HasState()) {
173                 App().PopState();
174         }
175 }
176
177
178 Assets::Assets()
179 : path("assets/")
180 , data_path(path + "data/")
181 , font_path(path + "fonts/")
182 , skin_path(path + "skins/")
183 , sky_path(path + "skies/")
184 , tile_path(path + "tiles/")
185 , random(0x6283B64CEFE57925)
186 , fonts{
187         graphics::Font(font_path + "DejaVuSans.ttf", 32),
188         graphics::Font(font_path + "DejaVuSans.ttf", 24),
189         graphics::Font(font_path + "DejaVuSans.ttf", 16)
190 } {
191         {
192                 std::ifstream resource_file(data_path + "resources");
193                 io::TokenStreamReader resource_reader(resource_file);
194                 ReadResources(resource_reader);
195         }
196
197         {
198                 std::ifstream tile_file(data_path + "tile_types");
199                 io::TokenStreamReader tile_reader(tile_file);
200                 ReadTileTypes(tile_reader);
201         }
202
203
204         graphics::Format format;
205         textures.tiles.Bind();
206         textures.tiles.Reserve(256, 256, 14, format);
207         LoadTileTexture("algae",    textures.tiles,  0);
208         LoadTileTexture("desert",   textures.tiles,  1);
209         LoadTileTexture("forest",   textures.tiles,  2);
210         LoadTileTexture("grass",    textures.tiles,  3);
211         LoadTileTexture("ice",      textures.tiles,  4);
212         LoadTileTexture("jungle",   textures.tiles,  5);
213         LoadTileTexture("mountain", textures.tiles,  6);
214         LoadTileTexture("ocean",    textures.tiles,  7);
215         LoadTileTexture("rock",     textures.tiles,  8);
216         LoadTileTexture("sand",     textures.tiles,  9);
217         LoadTileTexture("taiga",    textures.tiles, 10);
218         LoadTileTexture("tundra",   textures.tiles, 11);
219         LoadTileTexture("water",    textures.tiles, 12);
220         LoadTileTexture("wheat",    textures.tiles, 13);
221         textures.tiles.FilterTrilinear();
222
223         textures.skins.Bind();
224         textures.skins.Reserve(256, 256, 9, format);
225         LoadSkinTexture("plain", textures.skins, 0);
226         LoadSkinTexture("stripes", textures.skins, 1);
227         LoadSkinTexture("dots", textures.skins, 2);
228         LoadSkinTexture("lines", textures.skins, 3);
229         LoadSkinTexture("spots", textures.skins, 4);
230         LoadSkinTexture("circles", textures.skins, 5);
231         textures.skins.FilterTrilinear();
232
233         textures.sky.Bind();
234         LoadSkyTexture("blue", textures.sky);
235         textures.sky.FilterTrilinear();
236         textures.sky.WrapEdge();
237 }
238
239 Assets::~Assets() {
240 }
241
242 void Assets::ReadResources(io::TokenStreamReader &in) {
243         while (in.HasMore()) {
244                 string name;
245                 in.ReadIdentifier(name);
246                 in.Skip(io::Token::EQUALS);
247
248                 int id = 0;
249                 if (data.resources.Has(name)) {
250                         id = data.resources[name].id;
251                 } else {
252                         world::Resource res;
253                         res.name = name;
254                         id = data.resources.Add(res);
255                 }
256
257                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
258                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
259                         in.ReadIdentifier(name);
260                         in.Skip(io::Token::EQUALS);
261                         if (name == "label") {
262                                 in.ReadString(data.resources[id].label);
263                         } else if (name == "density") {
264                                 data.resources[id].density = in.GetDouble();
265                         } else if (name == "energy") {
266                                 data.resources[id].energy = in.GetDouble();
267                                 data.resources[id].inverse_energy = 1.0 / data.resources[id].energy;
268                         } else if (name == "state") {
269                                 in.ReadIdentifier(name);
270                                 if (name == "solid") {
271                                         data.resources[id].state = world::Resource::SOLID;
272                                 } else if (name == "liquid") {
273                                         data.resources[id].state = world::Resource::LIQUID;
274                                 } else if (name == "gas") {
275                                         data.resources[id].state = world::Resource::GAS;
276                                 } else if (name == "plasma") {
277                                         data.resources[id].state = world::Resource::PLASMA;
278                                 } else {
279                                         throw std::runtime_error("unknown resource state '" + name + "'");
280                                 }
281                         } else if (name == "base_color") {
282                                 in.ReadVec(data.resources[id].base_color);
283                         } else if (name == "compatibility") {
284                                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
285                                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
286                                         in.ReadIdentifier(name);
287                                         int sub_id = 0;
288                                         if (data.resources.Has(name)) {
289                                                 sub_id = data.resources[name].id;
290                                         } else {
291                                                 world::Resource res;
292                                                 res.name = name;
293                                                 sub_id = data.resources.Add(res);
294                                         }
295                                         in.Skip(io::Token::COLON);
296                                         double value = in.GetDouble();
297                                         in.Skip(io::Token::SEMICOLON);
298                                         data.resources[id].compatibility[sub_id] = value;
299                                 }
300                                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
301                         } else {
302                                 throw std::runtime_error("unknown resource property '" + name + "'");
303                         }
304                         in.Skip(io::Token::SEMICOLON);
305                 }
306                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
307                 in.Skip(io::Token::SEMICOLON);
308         }
309 }
310
311 void Assets::ReadTileTypes(io::TokenStreamReader &in) {
312         while (in.HasMore()) {
313                 string name;
314                 in.ReadIdentifier(name);
315                 in.Skip(io::Token::EQUALS);
316
317                 int id = 0;
318                 if (data.tile_types.Has(name)) {
319                         id = data.tile_types[name].id;
320                 } else {
321                         world::TileType type;
322                         type.name = name;
323                         id = data.tile_types.Add(type);
324                 }
325
326                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
327                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
328                         in.ReadIdentifier(name);
329                         in.Skip(io::Token::EQUALS);
330                         if (name == "label") {
331                                 in.ReadString(data.tile_types[id].label);
332                         } else if (name == "texture") {
333                                 data.tile_types[id].texture = in.GetInt();
334                         } else if (name == "yield") {
335                                 in.Skip(io::Token::BRACKET_OPEN);
336                                 while (in.Peek().type != io::Token::BRACKET_CLOSE) {
337                                         world::TileType::Yield yield;
338                                         in.Skip(io::Token::ANGLE_BRACKET_OPEN);
339                                         while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
340                                                 in.ReadIdentifier(name);
341                                                 in.Skip(io::Token::EQUALS);
342                                                 if (name == "resource") {
343                                                         in.ReadIdentifier(name);
344                                                         yield.resource = data.resources[name].id;
345                                                 } else if (name == "ubiquity") {
346                                                         yield.ubiquity = in.GetDouble();
347                                                 } else {
348                                                         throw std::runtime_error("unknown tile type yield property '" + name + "'");
349                                                 }
350                                                 in.Skip(io::Token::SEMICOLON);
351                                         }
352                                         in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
353                                         data.tile_types[id].resources.push_back(yield);
354                                         if (in.Peek().type == io::Token::COMMA) {
355                                                 in.Skip(io::Token::COMMA);
356                                         }
357                                 }
358                                 in.Skip(io::Token::BRACKET_CLOSE);
359                         } else {
360                                 throw std::runtime_error("unknown tile type property '" + name + "'");
361                         }
362                         in.Skip(io::Token::SEMICOLON);
363                 }
364                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
365                 in.Skip(io::Token::SEMICOLON);
366         }
367 }
368
369 void Assets::LoadTileTexture(const string &name, graphics::ArrayTexture &tex, int layer) const {
370         string path = tile_path + name + ".png";
371         SDL_Surface *srf = IMG_Load(path.c_str());
372         if (!srf) {
373                 throw SDLError("IMG_Load");
374         }
375         try {
376                 tex.Data(layer, *srf);
377         } catch (...) {
378                 SDL_FreeSurface(srf);
379                 throw;
380         }
381         SDL_FreeSurface(srf);
382 }
383
384 void Assets::LoadSkinTexture(const string &name, graphics::ArrayTexture &tex, int layer) const {
385         string path = skin_path + name + ".png";
386         SDL_Surface *srf = IMG_Load(path.c_str());
387         if (!srf) {
388                 throw SDLError("IMG_Load");
389         }
390         try {
391                 tex.Data(layer, *srf);
392         } catch (...) {
393                 SDL_FreeSurface(srf);
394                 throw;
395         }
396         SDL_FreeSurface(srf);
397 }
398
399 void Assets::LoadSkyTexture(const string &name, graphics::CubeMap &cm) const {
400         string full = sky_path + name;
401         string right = full + "-right.png";
402         string left = full + "-left.png";
403         string top = full + "-top.png";
404         string bottom = full + "-bottom.png";
405         string back = full + "-back.png";
406         string front = full + "-front.png";
407
408         SDL_Surface *srf = nullptr;
409
410         if (!(srf = IMG_Load(right.c_str()))) throw SDLError("IMG_Load");
411         try {
412                 cm.Data(graphics::CubeMap::RIGHT, *srf);
413         } catch (...) {
414                 SDL_FreeSurface(srf);
415                 throw;
416         }
417         SDL_FreeSurface(srf);
418
419         if (!(srf = IMG_Load(left.c_str()))) throw SDLError("IMG_Load");
420         try {
421                 cm.Data(graphics::CubeMap::LEFT, *srf);
422         } catch (...) {
423                 SDL_FreeSurface(srf);
424                 throw;
425         }
426         SDL_FreeSurface(srf);
427
428         if (!(srf = IMG_Load(top.c_str()))) throw SDLError("IMG_Load");
429         try {
430                 cm.Data(graphics::CubeMap::TOP, *srf);
431         } catch (...) {
432                 SDL_FreeSurface(srf);
433                 throw;
434         }
435         SDL_FreeSurface(srf);
436
437         if (!(srf = IMG_Load(bottom.c_str()))) throw SDLError("IMG_Load");
438         try {
439                 cm.Data(graphics::CubeMap::BOTTOM, *srf);
440         } catch (...) {
441                 SDL_FreeSurface(srf);
442                 throw;
443         }
444         SDL_FreeSurface(srf);
445
446         if (!(srf = IMG_Load(back.c_str()))) throw SDLError("IMG_Load");
447         try {
448                 cm.Data(graphics::CubeMap::BACK, *srf);
449         } catch (...) {
450                 SDL_FreeSurface(srf);
451                 throw;
452         }
453         SDL_FreeSurface(srf);
454
455         if (!(srf = IMG_Load(front.c_str()))) throw SDLError("IMG_Load");
456         try {
457                 cm.Data(graphics::CubeMap::FRONT, *srf);
458         } catch (...) {
459                 SDL_FreeSurface(srf);
460                 throw;
461         }
462         SDL_FreeSurface(srf);
463 }
464
465 void Assets::LoadUniverse(const string &name, world::Simulation &sim) const {
466         std::ifstream universe_file(data_path + name);
467         io::TokenStreamReader universe_reader(universe_file);
468         ReadBody(universe_reader, sim);
469         universe_reader.Skip(io::Token::SEMICOLON);
470 }
471
472 world::Body *Assets::ReadBody(io::TokenStreamReader &in, world::Simulation &sim) const {
473         std::unique_ptr<world::Body> body;
474         string name;
475         in.ReadIdentifier(name);
476         if (name == "Sun") {
477                 world::Sun *sun = new world::Sun;
478                 body.reset(sun);
479                 sim.AddSun(*sun);
480                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
481                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
482                         in.ReadIdentifier(name);
483                         in.Skip(io::Token::EQUALS);
484                         ReadSunProperty(name, in, *sun, sim);
485                         in.Skip(io::Token::SEMICOLON);
486                 }
487                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
488                 in.Skip(io::Token::SEMICOLON);
489         } else if (name == "Planet") {
490                 in.Skip(io::Token::PARENTHESIS_OPEN);
491                 int sidelength = in.GetInt();
492                 in.Skip(io::Token::PARENTHESIS_CLOSE);
493                 world::Planet *planet = new world::Planet(sidelength);
494                 sim.AddPlanet(*planet);
495                 body.reset(planet);
496                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
497                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
498                         in.ReadIdentifier(name);
499                         in.Skip(io::Token::EQUALS);
500                         ReadPlanetProperty(name, in, *planet, sim);
501                         in.Skip(io::Token::SEMICOLON);
502                 }
503                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
504         } else {
505                 throw std::runtime_error("unknown body class " + name);
506         }
507         return body.release();
508 }
509
510 void Assets::ReadSunProperty(const std::string &name, io::TokenStreamReader &in, world::Sun &sun, world::Simulation &sim) const {
511         if (name == "color") {
512                 glm::dvec3 color(0.0);
513                 in.ReadVec(color);
514                 sun.Color(color);
515         } else if (name == "luminosity") {
516                 sun.Luminosity(in.GetDouble());
517         } else {
518                 ReadBodyProperty(name, in, sun, sim);
519         }
520 }
521
522 void Assets::ReadPlanetProperty(const std::string &name, io::TokenStreamReader &in, world::Planet &planet, world::Simulation &sim) const {
523         if (name == "generate") {
524                 string gen;
525                 in.ReadIdentifier(gen);
526                 if (gen == "earthlike") {
527                         world::GenerateEarthlike(data.tile_types, planet);
528                 } else if (gen == "test") {
529                         world::GenerateTest(data.tile_types, planet);
530                 } else {
531                         throw std::runtime_error("unknown surface generator " + gen);
532                 }
533         } else if (name == "atmosphere") {
534                 string atm;
535                 in.ReadIdentifier(atm);
536                 planet.Atmosphere(data.resources[atm].id);
537         } else {
538                 ReadBodyProperty(name, in, planet, sim);
539         }
540 }
541
542 void Assets::ReadBodyProperty(const std::string &name, io::TokenStreamReader &in, world::Body &body, world::Simulation &sim) const {
543         if (name == "name") {
544                 string value;
545                 in.ReadString(value);
546                 body.Name(value);
547         } else if (name == "mass") {
548                 body.Mass(in.GetDouble());
549         } else if (name == "radius") {
550                 body.Radius(in.GetDouble());
551         } else if (name == "axial_tilt") {
552                 glm::dvec2 tilt(0.0);
553                 in.ReadVec(tilt);
554                 body.AxialTilt(tilt);
555         } else if (name == "rotation") {
556                 body.Rotation(in.GetDouble());
557         } else if (name == "angular_momentum") {
558                 body.AngularMomentum(in.GetDouble());
559         } else if (name == "orbit") {
560                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
561                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
562                         string oname;
563                         in.ReadIdentifier(oname);
564                         in.Skip(io::Token::EQUALS);
565                         if (oname == "SMA" || oname == "semi_major_axis") {
566                                 body.GetOrbit().SemiMajorAxis(in.GetDouble());
567                         } else if (oname == "ECC" || oname == "eccentricity") {
568                                 body.GetOrbit().Eccentricity(in.GetDouble());
569                         } else if (oname == "INC" || oname == "inclination") {
570                                 body.GetOrbit().Inclination(in.GetDouble());
571                         } else if (oname == "ASC" || oname == "ascending_node" || oname == "longitude_ascending") {
572                                 body.GetOrbit().LongitudeAscending(in.GetDouble());
573                         } else if (oname == "ARG" || oname == "APE" || oname == "argument_periapsis") {
574                                 body.GetOrbit().ArgumentPeriapsis(in.GetDouble());
575                         } else if (oname == "MNA" || oname == "mean_anomaly") {
576                                 body.GetOrbit().MeanAnomaly(in.GetDouble());
577                         } else {
578                                 throw std::runtime_error("unknown orbit property " + oname);
579                         }
580                         in.Skip(io::Token::SEMICOLON);
581                 }
582                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
583         } else if (name == "children") {
584                 in.Skip(io::Token::BRACKET_OPEN);
585                 while (in.Peek().type != io::Token::BRACKET_CLOSE) {
586                         world::Body *b = ReadBody(in, sim);
587                         b->SetParent(body);
588                         if (in.Peek().type == io::Token::COMMA) {
589                                 in.Skip(io::Token::COMMA);
590                         }
591                 }
592                 in.Skip(io::Token::BRACKET_CLOSE);
593         } else {
594                 throw std::runtime_error("unknown body property " + name);
595         }
596 }
597
598 }
599 }