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