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