]> git.localhorst.tv Git - blobs.git/blob - src/app/app.cpp
varying material and schlick/fresnel
[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 == "shiny") {
335                                 data.tile_types[id].shiny = in.GetDouble();
336                         } else if (name == "glossy") {
337                                 data.tile_types[id].glossy = in.GetDouble();
338                         } else if (name == "metallic") {
339                                 data.tile_types[id].metallic = in.GetDouble();
340                         } else if (name == "yield") {
341                                 in.Skip(io::Token::BRACKET_OPEN);
342                                 while (in.Peek().type != io::Token::BRACKET_CLOSE) {
343                                         world::TileType::Yield yield;
344                                         in.Skip(io::Token::ANGLE_BRACKET_OPEN);
345                                         while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
346                                                 in.ReadIdentifier(name);
347                                                 in.Skip(io::Token::EQUALS);
348                                                 if (name == "resource") {
349                                                         in.ReadIdentifier(name);
350                                                         yield.resource = data.resources[name].id;
351                                                 } else if (name == "ubiquity") {
352                                                         yield.ubiquity = in.GetDouble();
353                                                 } else {
354                                                         throw std::runtime_error("unknown tile type yield property '" + name + "'");
355                                                 }
356                                                 in.Skip(io::Token::SEMICOLON);
357                                         }
358                                         in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
359                                         data.tile_types[id].resources.push_back(yield);
360                                         if (in.Peek().type == io::Token::COMMA) {
361                                                 in.Skip(io::Token::COMMA);
362                                         }
363                                 }
364                                 in.Skip(io::Token::BRACKET_CLOSE);
365                         } else {
366                                 throw std::runtime_error("unknown tile type property '" + name + "'");
367                         }
368                         in.Skip(io::Token::SEMICOLON);
369                 }
370                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
371                 in.Skip(io::Token::SEMICOLON);
372         }
373 }
374
375 void Assets::LoadTileTexture(const string &name, graphics::ArrayTexture &tex, int layer) const {
376         string path = tile_path + name + ".png";
377         SDL_Surface *srf = IMG_Load(path.c_str());
378         if (!srf) {
379                 throw SDLError("IMG_Load");
380         }
381         try {
382                 tex.Data(layer, *srf);
383         } catch (...) {
384                 SDL_FreeSurface(srf);
385                 throw;
386         }
387         SDL_FreeSurface(srf);
388 }
389
390 void Assets::LoadSkinTexture(const string &name, graphics::ArrayTexture &tex, int layer) const {
391         string path = skin_path + name + ".png";
392         SDL_Surface *srf = IMG_Load(path.c_str());
393         if (!srf) {
394                 throw SDLError("IMG_Load");
395         }
396         try {
397                 tex.Data(layer, *srf);
398         } catch (...) {
399                 SDL_FreeSurface(srf);
400                 throw;
401         }
402         SDL_FreeSurface(srf);
403 }
404
405 void Assets::LoadSkyTexture(const string &name, graphics::CubeMap &cm) const {
406         string full = sky_path + name;
407         string right = full + "-right.png";
408         string left = full + "-left.png";
409         string top = full + "-top.png";
410         string bottom = full + "-bottom.png";
411         string back = full + "-back.png";
412         string front = full + "-front.png";
413
414         SDL_Surface *srf = nullptr;
415
416         if (!(srf = IMG_Load(right.c_str()))) throw SDLError("IMG_Load");
417         try {
418                 cm.Data(graphics::CubeMap::RIGHT, *srf);
419         } catch (...) {
420                 SDL_FreeSurface(srf);
421                 throw;
422         }
423         SDL_FreeSurface(srf);
424
425         if (!(srf = IMG_Load(left.c_str()))) throw SDLError("IMG_Load");
426         try {
427                 cm.Data(graphics::CubeMap::LEFT, *srf);
428         } catch (...) {
429                 SDL_FreeSurface(srf);
430                 throw;
431         }
432         SDL_FreeSurface(srf);
433
434         if (!(srf = IMG_Load(top.c_str()))) throw SDLError("IMG_Load");
435         try {
436                 cm.Data(graphics::CubeMap::TOP, *srf);
437         } catch (...) {
438                 SDL_FreeSurface(srf);
439                 throw;
440         }
441         SDL_FreeSurface(srf);
442
443         if (!(srf = IMG_Load(bottom.c_str()))) throw SDLError("IMG_Load");
444         try {
445                 cm.Data(graphics::CubeMap::BOTTOM, *srf);
446         } catch (...) {
447                 SDL_FreeSurface(srf);
448                 throw;
449         }
450         SDL_FreeSurface(srf);
451
452         if (!(srf = IMG_Load(back.c_str()))) throw SDLError("IMG_Load");
453         try {
454                 cm.Data(graphics::CubeMap::BACK, *srf);
455         } catch (...) {
456                 SDL_FreeSurface(srf);
457                 throw;
458         }
459         SDL_FreeSurface(srf);
460
461         if (!(srf = IMG_Load(front.c_str()))) throw SDLError("IMG_Load");
462         try {
463                 cm.Data(graphics::CubeMap::FRONT, *srf);
464         } catch (...) {
465                 SDL_FreeSurface(srf);
466                 throw;
467         }
468         SDL_FreeSurface(srf);
469 }
470
471 void Assets::LoadUniverse(const string &name, world::Simulation &sim) const {
472         std::ifstream universe_file(data_path + name);
473         io::TokenStreamReader universe_reader(universe_file);
474         ReadBody(universe_reader, sim);
475         universe_reader.Skip(io::Token::SEMICOLON);
476 }
477
478 world::Body *Assets::ReadBody(io::TokenStreamReader &in, world::Simulation &sim) const {
479         std::unique_ptr<world::Body> body;
480         string name;
481         in.ReadIdentifier(name);
482         if (name == "Sun") {
483                 world::Sun *sun = new world::Sun;
484                 body.reset(sun);
485                 sim.AddSun(*sun);
486                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
487                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
488                         in.ReadIdentifier(name);
489                         in.Skip(io::Token::EQUALS);
490                         ReadSunProperty(name, in, *sun, sim);
491                         in.Skip(io::Token::SEMICOLON);
492                 }
493                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
494                 in.Skip(io::Token::SEMICOLON);
495         } else if (name == "Planet") {
496                 in.Skip(io::Token::PARENTHESIS_OPEN);
497                 int sidelength = in.GetInt();
498                 in.Skip(io::Token::PARENTHESIS_CLOSE);
499                 world::Planet *planet = new world::Planet(sidelength);
500                 sim.AddPlanet(*planet);
501                 body.reset(planet);
502                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
503                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
504                         in.ReadIdentifier(name);
505                         in.Skip(io::Token::EQUALS);
506                         ReadPlanetProperty(name, in, *planet, sim);
507                         in.Skip(io::Token::SEMICOLON);
508                 }
509                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
510         } else {
511                 throw std::runtime_error("unknown body class " + name);
512         }
513         return body.release();
514 }
515
516 void Assets::ReadSunProperty(const std::string &name, io::TokenStreamReader &in, world::Sun &sun, world::Simulation &sim) const {
517         if (name == "color") {
518                 glm::dvec3 color(0.0);
519                 in.ReadVec(color);
520                 sun.Color(color);
521         } else if (name == "luminosity") {
522                 sun.Luminosity(in.GetDouble());
523         } else {
524                 ReadBodyProperty(name, in, sun, sim);
525         }
526 }
527
528 void Assets::ReadPlanetProperty(const std::string &name, io::TokenStreamReader &in, world::Planet &planet, world::Simulation &sim) const {
529         if (name == "generate") {
530                 string gen;
531                 in.ReadIdentifier(gen);
532                 if (gen == "earthlike") {
533                         world::GenerateEarthlike(data.tile_types, planet);
534                 } else if (gen == "test") {
535                         world::GenerateTest(data.tile_types, planet);
536                 } else {
537                         throw std::runtime_error("unknown surface generator " + gen);
538                 }
539         } else if (name == "atmosphere") {
540                 string atm;
541                 in.ReadIdentifier(atm);
542                 planet.Atmosphere(data.resources[atm].id);
543         } else {
544                 ReadBodyProperty(name, in, planet, sim);
545         }
546 }
547
548 void Assets::ReadBodyProperty(const std::string &name, io::TokenStreamReader &in, world::Body &body, world::Simulation &sim) const {
549         if (name == "name") {
550                 string value;
551                 in.ReadString(value);
552                 body.Name(value);
553         } else if (name == "mass") {
554                 body.Mass(in.GetDouble());
555         } else if (name == "radius") {
556                 body.Radius(in.GetDouble());
557         } else if (name == "axial_tilt") {
558                 glm::dvec2 tilt(0.0);
559                 in.ReadVec(tilt);
560                 body.AxialTilt(tilt);
561         } else if (name == "rotation") {
562                 body.Rotation(in.GetDouble());
563         } else if (name == "angular_momentum") {
564                 body.AngularMomentum(in.GetDouble());
565         } else if (name == "orbit") {
566                 in.Skip(io::Token::ANGLE_BRACKET_OPEN);
567                 while (in.Peek().type != io::Token::ANGLE_BRACKET_CLOSE) {
568                         string oname;
569                         in.ReadIdentifier(oname);
570                         in.Skip(io::Token::EQUALS);
571                         if (oname == "SMA" || oname == "semi_major_axis") {
572                                 body.GetOrbit().SemiMajorAxis(in.GetDouble());
573                         } else if (oname == "ECC" || oname == "eccentricity") {
574                                 body.GetOrbit().Eccentricity(in.GetDouble());
575                         } else if (oname == "INC" || oname == "inclination") {
576                                 body.GetOrbit().Inclination(in.GetDouble());
577                         } else if (oname == "ASC" || oname == "ascending_node" || oname == "longitude_ascending") {
578                                 body.GetOrbit().LongitudeAscending(in.GetDouble());
579                         } else if (oname == "ARG" || oname == "APE" || oname == "argument_periapsis") {
580                                 body.GetOrbit().ArgumentPeriapsis(in.GetDouble());
581                         } else if (oname == "MNA" || oname == "mean_anomaly") {
582                                 body.GetOrbit().MeanAnomaly(in.GetDouble());
583                         } else {
584                                 throw std::runtime_error("unknown orbit property " + oname);
585                         }
586                         in.Skip(io::Token::SEMICOLON);
587                 }
588                 in.Skip(io::Token::ANGLE_BRACKET_CLOSE);
589         } else if (name == "children") {
590                 in.Skip(io::Token::BRACKET_OPEN);
591                 while (in.Peek().type != io::Token::BRACKET_CLOSE) {
592                         world::Body *b = ReadBody(in, sim);
593                         b->SetParent(body);
594                         if (in.Peek().type == io::Token::COMMA) {
595                                 in.Skip(io::Token::COMMA);
596                         }
597                 }
598                 in.Skip(io::Token::BRACKET_CLOSE);
599         } else {
600                 throw std::runtime_error("unknown body property " + name);
601         }
602 }
603
604 }
605 }