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