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