]> git.localhorst.tv Git - blank.git/blob - src/ui/ui.cpp
state management and control
[blank.git] / src / ui / ui.cpp
1 #include "HUD.hpp"
2 #include "Interface.hpp"
3
4 #include "../app/Assets.hpp"
5 #include "../app/Environment.hpp"
6 #include "../app/FrameCounter.hpp"
7 #include "../app/init.hpp"
8 #include "../audio/Audio.hpp"
9 #include "../graphics/Font.hpp"
10 #include "../graphics/Viewport.hpp"
11 #include "../model/shapes.hpp"
12 #include "../world/World.hpp"
13
14 #include <algorithm>
15 #include <cmath>
16 #include <iostream>
17 #include <sstream>
18 #include <glm/gtc/matrix_transform.hpp>
19 #include <glm/gtx/io.hpp>
20
21
22 namespace blank {
23
24 HUD::HUD(const BlockTypeRegistry &types, const Font &font)
25 : types(types)
26 , font(font)
27 , block()
28 , block_buf()
29 , block_transform(1.0f)
30 , block_label()
31 , block_visible(false)
32 , crosshair() {
33         block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
34         block_transform = glm::scale(block_transform, glm::vec3(50.0f));
35         block_transform = glm::rotate(block_transform, 3.5f, glm::vec3(1.0f, 0.0f, 0.0f));
36         block_transform = glm::rotate(block_transform, 0.35f, glm::vec3(0.0f, 1.0f, 0.0f));
37
38         OutlineModel::Buffer buf;
39         buf.vertices = std::vector<glm::vec3>({
40                 { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
41                 {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
42         });
43         buf.indices = std::vector<OutlineModel::Index>({
44                 0, 1, 2, 3
45         });
46         buf.colors.resize(4, { 10.0f, 10.0f, 10.0f });
47         crosshair.Update(buf);
48
49         block_label.Position(
50                 glm::vec3(50.0f, 85.0f, 0.0f),
51                 Gravity::NORTH_WEST,
52                 Gravity::NORTH
53         );
54         block_label.Foreground(glm::vec4(1.0f));
55         block_label.Background(glm::vec4(0.5f));
56 }
57
58
59 void HUD::Display(const Block &b) {
60         const BlockType &type = types.Get(b.type);
61
62         block_buf.Clear();
63         type.FillEntityModel(block_buf, b.Transform());
64         block.Update(block_buf);
65
66         block_label.Set(font, type.label);
67
68         block_visible = type.visible;
69 }
70
71
72 void HUD::Render(Viewport &viewport) noexcept {
73         viewport.ClearDepth();
74
75         DirectionalLighting &world_prog = viewport.HUDProgram();
76         world_prog.SetLightDirection({ 1.0f, 3.0f, 5.0f });
77         // disable distance fog
78         world_prog.SetFogDensity(0.0f);
79
80         viewport.EnableInvertBlending();
81         viewport.SetCursor(glm::vec3(0.0f), Gravity::CENTER);
82         world_prog.SetM(viewport.Cursor());
83         crosshair.Draw();
84
85         if (block_visible) {
86                 viewport.DisableBlending();
87                 world_prog.SetM(block_transform);
88                 block.Draw();
89                 block_label.Render(viewport);
90         }
91 }
92
93
94 Interface::Interface(
95         const Config &config,
96         Environment &env,
97         World &world)
98 : env(env)
99 , world(world)
100 , ctrl(world.Player())
101 , font(env.assets.LoadFont("DejaVuSans", 16))
102 , hud(world.BlockTypes(), font)
103 , aim{{ 0, 0, 0 }, { 0, 0, -1 }}
104 , aim_chunk(nullptr)
105 , aim_block(0)
106 , aim_normal()
107 , outline()
108 , outline_transform(1.0f)
109 , counter_text()
110 , messages(font)
111 , msg_timer(5000)
112 , config(config)
113 , place_timer(256)
114 , remove_timer(256)
115 , remove(0)
116 , selection(1)
117 , place_sound(env.assets.LoadSound("thump"))
118 , remove_sound(env.assets.LoadSound("plop"))
119 , fwd(0)
120 , rev(0) {
121         counter_text.Hide();
122         counter_text.Position(glm::vec3(-25.0f, 25.0f, 0.0f), Gravity::NORTH_EAST);
123         counter_text.Foreground(glm::vec4(1.0f));
124         counter_text.Background(glm::vec4(0.5f));
125         position_text.Hide();
126         position_text.Position(glm::vec3(-25.0f, 25.0f + font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
127         position_text.Foreground(glm::vec4(1.0f));
128         position_text.Background(glm::vec4(0.5f));
129         messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST);
130         messages.Foreground(glm::vec4(1.0f));
131         messages.Background(glm::vec4(0.5f));
132         hud.Display(selection);
133 }
134
135
136 void Interface::HandlePress(const SDL_KeyboardEvent &event) {
137         if (config.keyboard_disabled) return;
138
139         switch (event.keysym.sym) {
140                 case SDLK_w:
141                         rev.z = 1;
142                         break;
143                 case SDLK_s:
144                         fwd.z = 1;
145                         break;
146                 case SDLK_a:
147                         rev.x = 1;
148                         break;
149                 case SDLK_d:
150                         fwd.x = 1;
151                         break;
152                 case SDLK_SPACE:
153                         fwd.y = 1;
154                         break;
155                 case SDLK_LSHIFT:
156                         rev.y = 1;
157                         break;
158
159                 case SDLK_q:
160                         FaceBlock();
161                         break;
162                 case SDLK_e:
163                         TurnBlock();
164                         break;
165
166                 case SDLK_n:
167                         ToggleCollision();
168                         break;
169
170                 case SDLK_b:
171                         PrintBlockInfo();
172                         break;
173                 case SDLK_c:
174                         PrintChunkInfo();
175                         break;
176                 case SDLK_l:
177                         PrintLightInfo();
178                         break;
179                 case SDLK_p:
180                         PrintSelectionInfo();
181                         break;
182
183                 case SDLK_F1:
184                         ToggleVisual();
185                         break;
186                 case SDLK_F3:
187                         ToggleDebug();
188                         break;
189                 case SDLK_F4:
190                         ToggleAudio();
191                         break;
192         }
193 }
194
195 void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
196         if (config.keyboard_disabled) return;
197
198         switch (event.keysym.sym) {
199                 case SDLK_w:
200                         rev.z = 0;
201                         break;
202                 case SDLK_s:
203                         fwd.z = 0;
204                         break;
205                 case SDLK_a:
206                         rev.x = 0;
207                         break;
208                 case SDLK_d:
209                         fwd.x = 0;
210                         break;
211                 case SDLK_SPACE:
212                         fwd.y = 0;
213                         break;
214                 case SDLK_LSHIFT:
215                         rev.y = 0;
216                         break;
217         }
218 }
219
220 void Interface::FaceBlock() {
221         selection.SetFace(Block::Face((selection.GetFace() + 1) % Block::FACE_COUNT));
222         hud.Display(selection);
223 }
224
225 void Interface::TurnBlock() {
226         selection.SetTurn(Block::Turn((selection.GetTurn() + 1) % Block::TURN_COUNT));
227         hud.Display(selection);
228 }
229
230 void Interface::ToggleCollision() {
231         ctrl.Controlled().WorldCollidable(!ctrl.Controlled().WorldCollidable());
232         if (ctrl.Controlled().WorldCollidable()) {
233                 PostMessage("collision on");
234         } else {
235                 PostMessage("collision off");
236         }
237 }
238
239 void Interface::PrintBlockInfo() {
240         std::cout << std::endl;
241         if (!aim_chunk) {
242                 PostMessage("not looking at any block");
243                 Ray aim = ctrl.Aim();
244                 std::stringstream s;
245                 s << "aim ray: " << aim.orig << ", " << aim.dir;
246                 PostMessage(s.str());
247                 return;
248         }
249         std::stringstream s;
250         s << "looking at block " << aim_block
251                 << " " << Chunk::ToCoords(aim_block)
252                 << " of chunk " << aim_chunk->Position()
253         ;
254         PostMessage(s.str());
255         Print(aim_chunk->BlockAt(aim_block));
256 }
257
258 void Interface::PrintChunkInfo() {
259         std::cout << std::endl;
260         if (!aim_chunk) {
261                 PostMessage("not looking at any block");
262                 return;
263         }
264         std::stringstream s;
265         s << "looking at chunk " << aim_chunk->Position();
266         PostMessage(s.str());
267
268         PostMessage("  neighbors:");
269         if (aim_chunk->HasNeighbor(Block::FACE_LEFT)) {
270                 s.str("");
271                 s << " left  " << aim_chunk->GetNeighbor(Block::FACE_LEFT).Position();
272                 PostMessage(s.str());
273         }
274         if (aim_chunk->HasNeighbor(Block::FACE_RIGHT)) {
275                 s.str("");
276                 s << " right " << aim_chunk->GetNeighbor(Block::FACE_RIGHT).Position();
277                 PostMessage(s.str());
278         }
279         if (aim_chunk->HasNeighbor(Block::FACE_UP)) {
280                 s.str("");
281                 s << " up    " << aim_chunk->GetNeighbor(Block::FACE_UP).Position();
282                 PostMessage(s.str());
283         }
284         if (aim_chunk->HasNeighbor(Block::FACE_DOWN)) {
285                 s.str("");
286                 s << " down  " << aim_chunk->GetNeighbor(Block::FACE_DOWN).Position();
287                 PostMessage(s.str());
288         }
289         if (aim_chunk->HasNeighbor(Block::FACE_FRONT)) {
290                 s.str("");
291                 s << " front " << aim_chunk->GetNeighbor(Block::FACE_FRONT).Position();
292                 PostMessage(s.str());
293         }
294         if (aim_chunk->HasNeighbor(Block::FACE_BACK)) {
295                 s.str("");
296                 s << " back  " << aim_chunk->GetNeighbor(Block::FACE_BACK).Position();
297                 PostMessage(s.str());
298         }
299         std::cout << std::endl;
300 }
301
302 void Interface::PrintLightInfo() {
303         std::stringstream s;
304         s
305                 << "light level " << world.PlayerChunk().GetLight(world.Player().Position())
306                 << " at position " << world.Player().Position()
307         ;
308         PostMessage(s.str());
309 }
310
311 void Interface::PrintSelectionInfo() {
312         std::cout << std::endl;
313         Print(selection);
314 }
315
316 void Interface::Print(const Block &block) {
317         std::stringstream s;
318         s << "type: " << block.type
319                 << ", face: " << block.GetFace()
320                 << ", turn: " << block.GetTurn()
321         ;
322         PostMessage(s.str());
323 }
324
325 void Interface::ToggleAudio() {
326         config.audio_disabled = !config.audio_disabled;
327         if (config.audio_disabled) {
328                 PostMessage("audio off");
329         } else {
330                 PostMessage("audio on");
331         }
332 }
333
334 void Interface::ToggleVisual() {
335         config.visual_disabled = !config.visual_disabled;
336         if (config.visual_disabled) {
337                 PostMessage("visual off");
338         } else {
339                 PostMessage("visual on");
340         }
341 }
342
343 void Interface::ToggleDebug() {
344         counter_text.Toggle();
345         position_text.Toggle();
346         if (counter_text.Visible()) {
347                 UpdateCounter();
348                 UpdatePosition();
349         }
350 }
351
352 void Interface::UpdateCounter() {
353         std::stringstream s;
354         s << std::setprecision(3) <<
355                 "avg: " << env.counter.Average().running << "ms, "
356                 "peak: " << env.counter.Peak().running << "ms";
357         std::string text = s.str();
358         counter_text.Set(font, text);
359 }
360
361 void Interface::UpdatePosition() {
362         std::stringstream s;
363         s << std::setprecision(3) << "pos: " << ctrl.Controlled().AbsolutePosition();
364         position_text.Set(font, s.str());
365 }
366
367
368 void Interface::Handle(const SDL_MouseMotionEvent &event) {
369         if (config.mouse_disabled) return;
370         ctrl.RotateYaw(event.xrel * config.yaw_sensitivity);
371         ctrl.RotatePitch(event.yrel * config.pitch_sensitivity);
372 }
373
374 void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
375         if (config.mouse_disabled) return;
376
377         if (event.button == SDL_BUTTON_LEFT) {
378                 RemoveBlock();
379                 remove_timer.Start();
380         } else if (event.button == SDL_BUTTON_MIDDLE) {
381                 PickBlock();
382         } else if (event.button == SDL_BUTTON_RIGHT) {
383                 PlaceBlock();
384                 place_timer.Start();
385         }
386 }
387
388 void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
389         if (config.mouse_disabled) return;
390
391         if (event.button == SDL_BUTTON_LEFT) {
392                 remove_timer.Stop();
393         } else if (event.button == SDL_BUTTON_RIGHT) {
394                 place_timer.Stop();
395         }
396 }
397
398 void Interface::PickBlock() {
399         if (!aim_chunk) return;
400         selection = aim_chunk->BlockAt(aim_block);
401         hud.Display(selection);
402 }
403
404 void Interface::PlaceBlock() {
405         if (!aim_chunk) return;
406         Chunk *mod_chunk = aim_chunk;
407         glm::vec3 next_pos = Chunk::ToCoords(aim_block) + aim_normal;
408         if (!Chunk::InBounds(next_pos)) {
409                 mod_chunk = &world.Next(*aim_chunk, aim_normal);
410                 next_pos -= aim_normal * glm::vec3(Chunk::Extent());
411         }
412         mod_chunk->SetBlock(next_pos, selection);
413         mod_chunk->Invalidate();
414
415         if (config.audio_disabled) return;
416         const Entity &player = ctrl.Controlled();
417         env.audio.Play(
418                 place_sound,
419                 mod_chunk->ToSceneCoords(player.ChunkCoords(), next_pos)
420         );
421 }
422
423 void Interface::RemoveBlock() noexcept {
424         if (!aim_chunk) return;
425         aim_chunk->SetBlock(aim_block, remove);
426         aim_chunk->Invalidate();
427
428         if (config.audio_disabled) return;
429         const Entity &player = ctrl.Controlled();
430         env.audio.Play(
431                 remove_sound,
432                 aim_chunk->ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(aim_block))
433         );
434 }
435
436
437 void Interface::Handle(const SDL_MouseWheelEvent &event) {
438         if (config.mouse_disabled) return;
439
440         if (event.y < 0) {
441                 SelectNext();
442         } else if (event.y > 0) {
443                 SelectPrevious();
444         }
445 }
446
447 void Interface::SelectNext() {
448         ++selection.type;
449         if (size_t(selection.type) >= world.BlockTypes().Size()) {
450                 selection.type = 1;
451         }
452         hud.Display(selection);
453 }
454
455 void Interface::SelectPrevious() {
456         --selection.type;
457         if (selection.type <= 0) {
458                 selection.type = world.BlockTypes().Size() - 1;
459         }
460         hud.Display(selection);
461 }
462
463
464 void Interface::PostMessage(const char *msg) {
465         messages.PushLine(msg);
466         msg_timer.Reset();
467         msg_timer.Start();
468         std::cout << msg << std::endl;
469 }
470
471
472 void Interface::Update(int dt) {
473         ctrl.Velocity(glm::vec3(fwd - rev) * config.move_velocity);
474         ctrl.Update(dt);
475
476         msg_timer.Update(dt);
477         place_timer.Update(dt);
478         remove_timer.Update(dt);
479
480         aim = ctrl.Aim();
481         CheckAim();
482
483         if (msg_timer.HitOnce()) {
484                 msg_timer.Stop();
485         }
486
487         if (remove_timer.Hit()) {
488                 RemoveBlock();
489                 CheckAim();
490         }
491
492         if (place_timer.Hit()) {
493                 PlaceBlock();
494                 CheckAim();
495         }
496
497         if (counter_text.Visible() && env.counter.Changed()) {
498                 UpdateCounter();
499         }
500         if (position_text.Visible()) {
501                 UpdatePosition();
502         }
503 }
504
505 namespace {
506
507 OutlineModel::Buffer outl_buf;
508
509 }
510
511 void Interface::CheckAim() {
512         float dist;
513         if (world.Intersection(aim, glm::mat4(1.0f), aim_chunk, aim_block, dist, aim_normal)) {
514                 outl_buf.Clear();
515                 aim_chunk->Type(aim_chunk->BlockAt(aim_block)).FillOutlineModel(outl_buf);
516                 outline.Update(outl_buf);
517                 outline_transform = glm::scale(glm::vec3(1.0002f));
518                 outline_transform *= aim_chunk->Transform(world.Player().ChunkCoords());
519                 outline_transform *= aim_chunk->ToTransform(Chunk::ToPos(aim_block), aim_block);
520         } else {
521                 aim_chunk = nullptr;
522         }
523 }
524
525
526 void Interface::Render(Viewport &viewport) noexcept {
527         if (config.visual_disabled) return;
528
529         if (aim_chunk) {
530                 DirectionalLighting &world_prog = viewport.EntityProgram();
531                 world_prog.SetM(outline_transform);
532                 outline.Draw();
533         }
534
535         if (counter_text.Visible()) {
536                 counter_text.Render(viewport);
537         }
538         if (position_text.Visible()) {
539                 position_text.Render(viewport);
540         }
541
542         if (msg_timer.Running()) {
543                 messages.Render(viewport);
544         }
545
546         hud.Render(viewport);
547 }
548
549 }