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