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