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