aboutsummaryrefslogtreecommitdiff
path: root/src/client/render/draw.cc
blob: d9ea9c3f001ef86b0b37c4535a687925b3ddac98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
#include "client/render/draw.hh"

namespace client {
namespace render {

static void draw_state_info(const shared::player& lp) noexcept {

    constexpr auto nstr = [](const auto d) -> std::string {
        auto str = std::to_string(d);
        str.erase(std::prev(std::end(str), 4), std::end(str));
        return str;
    };

    const auto make_position = [&]() -> std::string {
        const auto chunk_x = lp.get_chunk_pos().x;
        const auto chunk_z = lp.get_chunk_pos().z;
        const auto x = static_cast<double>(lp.get_local_pos().x);
        const auto y = static_cast<double>(lp.get_local_pos().y);
        const auto z = static_cast<double>(lp.get_local_pos().z);
        constexpr auto WIDTH = shared::world::chunk::WIDTH;
        const auto abs_x =
            static_cast<double>(lp.get_chunk_pos().x) * WIDTH + x;
        const auto abs_z =
            static_cast<double>(lp.get_chunk_pos().z) * WIDTH + z;

        constexpr auto nstr = [](const double d) -> std::string {
            auto str = std::to_string(d);
            str.erase(std::prev(std::end(str), 4), std::end(str));
            return str;
        };
        const std::string chk_str = '[' + std::to_string(chunk_x) + ", " +
                                    std::to_string(chunk_z) + ']';
        const std::string abs_str =
            '(' + nstr(abs_x) + ", " + nstr(y) + ", " + nstr(abs_z) + ')';
        return "position: " + abs_str + chk_str;
    };

    const auto make_direction = [&]() -> std::string {
        const float yaw = glm::degrees(lp.get_angles().yaw);
        const float pitch = glm::degrees(lp.get_angles().pitch);

        const std::string bearing = [&]() -> std::string {
            if (yaw > 135.0f || yaw < -135.0f) {
                return "South";
            } else if (yaw > 45.0f) {
                return "East";
            } else if (yaw > -45.0f) {
                return "North";
            } else {
                return "West";
            }
        }();
        const std::string face_str = '(' + nstr(pitch) + ", " + nstr(yaw) + ')';
        // Yaw is between 0 and 360.
        return "perspective: " + face_str + " -> " + bearing;
    };

    const auto make_address = []() -> std::string {
        const std::string address = std::string(client::state::address);
        const std::string port = std::string(client::state::port);
        return "address: " + address + ":" + port;
    };

    const auto make_tickrate = [&]() -> std::string {
        return "tickrate: " + std::to_string(state::tickrate);
    };

    const auto make_seed = []() -> std::string {
        return "seed: " + std::to_string(client::state::seed);
    };

    const auto make_entities = []() -> std::string {
        const auto index = std::to_string(*client::state::localplayer_index);
        return "index: " + index;
    };

    const auto make_chunks = []() -> std::string {
        return "chunks: " +
               std::to_string(client::state::requested_chunk_count) + ", " +
               std::to_string(client::state::networked_chunk_count) + " @ " +
               std::to_string(client::state::draw_distance) + " draw distance";
    };

    const auto make_speed = [&]() -> std::string {
        return "speed: " + nstr(glm::length(lp.get_velocity())) + " b/s";
    };

    const auto make_velocity = [&]() -> std::string {
        return "velocity: (" + nstr(lp.get_velocity().x) + ", " +
               nstr(lp.get_velocity().y) + ", " + nstr(lp.get_velocity().z) +
               ")";
    };

    const auto make_msg = [&]() -> std::string {
        const auto& message = get_localplayer().get_message();
        return "message: " + (message.has_value() ? message->text : "");
    };

    const auto make_biome = [&]() -> std::string {
        const int x = static_cast<int>(lp.get_local_pos().x);
        const int z = static_cast<int>(lp.get_local_pos().z);
        const auto is_inside = [](const int val) {
            return std::clamp(val, 0, shared::world::chunk::WIDTH) == val;
        };
        if (!is_inside(x) || !is_inside(z)) {
            return "?";
        }

        const auto find_it = client::state::chunks.find(lp.get_chunk_pos());
        if (find_it == std::end(client::state::chunks)) {
            return "?";
        }
        const auto& chunk = find_it->second;
        if (!chunk.has_value()) {
            return "?";
        }
        return std::string{"biome: "} + chunk->get_biome(x, z);
    };

    // Draws all of our strings and works its way down the top of the screen.
    text_args args{.pos = {.extent = {0.0f, 1.0f - 0.015f}},
                   .extent_height = 0.0165f,
                   .colour = {1.0f, 1.0f, 1.0f, 1.0f},
                   .has_backing = true};
    const auto draw_str_trail = [&args](const std::string& str) {
        draw_text(str, args);
        args.pos.extent.y -= 0.02f;
    };
    draw_str_trail("blockgame_linux v0.22");
    draw_str_trail(make_address());
    draw_str_trail(make_tickrate());
    draw_str_trail(make_seed());
    draw_str_trail(make_entities());
    draw_str_trail(make_chunks());
    draw_str_trail(make_position());
    draw_str_trail(make_direction());
    draw_str_trail(make_speed());
    draw_str_trail(make_velocity());
    draw_str_trail(make_biome());
    draw_str_trail(make_msg());
}

static void draw_fps() noexcept {
    const auto get_fps_colour = [](const int fps) -> glm::vec4 {
        if (fps == 0) {
            return {1.0f, 1.0f, 1.0f, 1.0f};
        } else if (fps < 30) {
            return {1.0f, 0.0f, 0.0f, 1.0f};
        } else if (fps < 60) {
            return {1.0f, 0.5f, 0.0f, 1.0f};
        } else if (fps < 120) {
            return {0.0f, 1.0f, 0.0f, 1.0f};
        }
        return {0.0f, 1.0f, 1.0f, 1.0f};
    };

    const int fps = client::render::get_fps();
    const std::string fps_str = fps != 0 ? std::to_string(fps) : "~";
    draw_text("FPS: " + fps_str, {.pos = relative_arg{.offset = {0.0f, 2.0f}},
                                  .extent_height = 0.0165f,
                                  .colour = get_fps_colour(fps),
                                  .has_backing = true});
}

// Player position, version, fps etc
// Just don't read past this point, doing this is unbelievably ugly.
// I don't know why I write this like someone else will read it.
static void draw_overlay(const shared::player& lp) noexcept {
    draw_state_info(lp);
    draw_fps();
}

static void draw_main_pass(const client::player& localplayer,
                           client::entities_t& entities,
                           client::world::chunks_t& chunks) noexcept {

    {
        // zero means no limit
        const unsigned chunks_per_frame =
            client::settings::get<unsigned>({"video", "chunks_per_frame"}, 0u);
        unsigned chunks_regenerated = 0u;
        for (auto& chunk : chunks) {
            if (!chunk.second.has_value()) {
                continue;
            }

            const bool skip = chunks_per_frame == 0
                                  ? false
                                  : chunks_regenerated >= chunks_per_frame;
            chunks_regenerated += chunk.second->draw(
                chunks, localplayer, world::chunk::pass::solid, skip);
        }
    }

    for (const auto& [index, entity_ptr] : entities) {
        if (index == localplayer.get_index()) {
            continue;
        }
        entity_ptr->draw(localplayer);
    }
}

static void draw_water_pass(const client::player& localplayer,
                            client::world::chunks_t& chunks) noexcept {
    for (auto& chunk : chunks) {
        if (!chunk.second.has_value()) {
            continue;
        }
        chunk.second->draw(chunks, localplayer,
                           client::world::chunk::pass::water);
    }
}

static void draw_wts_text(const client::player& localplayer,
                          client::entities_t& entities) noexcept {
    for (const auto& [index, entity_ptr] : entities) {
        if (index == localplayer.get_index()) {
            continue;
        }
        entity_ptr->draw_wts(localplayer);
    }
}

static void draw_hud(const client::player& localplayer,
                     const client::world::chunks_t& chunks) noexcept {
    const auto should_draw_hud = !client::input::is_key_toggled(SDLK_F1);
    if (client::input::is_key_toggled(SDLK_F3) && should_draw_hud) {
        draw_overlay(localplayer);
    }

    if (client::window::is_open()) {
        return;
    }

    if (!should_draw_hud) {
        return;
    }

    // crosshair
    client::render::draw_rectangle(
        {.pos = {.extent = {0.5f, 0.5f}, .offset = {-3.0f, -3.0f}},
         .size = {.offset = {6.0f, 6.0f}},
         .colour = {1.0f, 1.0f, 1.0f, 1.0f}});

    // Draw the outline of the block we're aiming at.
    if (const auto interact = client::movement::interact(
            localplayer, movement::interact_mode::remove, chunks);
        interact.has_value()) {

        const auto [world_x, world_z] = [&]() -> std::pair<float, float> {
            const float offset_x = static_cast<float>(
                interact->first.x - localplayer.get_chunk_pos().x);
            const float offset_z = static_cast<float>(
                interact->first.z - localplayer.get_chunk_pos().z);
            return {offset_x * shared::world::chunk::WIDTH,
                    offset_z * shared::world::chunk::WIDTH};
        }();

        const glm::vec3 render_pos = {
            world_x + static_cast<float>(interact->second.x),
            interact->second.y,
            world_z + static_cast<float>(interact->second.z)};

        client::render::render_cube_outline(render_pos + 0.5f,
                                            {0.8f, 0.8f, 0.8f, 1.0f});
    }
}

static void update_camera(const shared::player& localplayer) noexcept {
    // approximately encompasses everything we could possibly see

    // if our zfar is too low, our fog breaks
    // bit of maths for calculating the max distance we could can see
    client::render::camera::get_zfar() = std::hypot(
        static_cast<float>(world::chunk::HEIGHT),
        float(client::state::draw_distance) * std::hypot(16.0f, 16.0f));
    client::render::camera::get_pos() =
        localplayer.get_local_pos() +
        glm::vec3{0.0f, shared::player::EYE_HEIGHT, 0.0f};
    client::render::camera::get_front() = localplayer.get_angles().to_dir();
    client::render::camera::update();
}

// We create framebuffer here to enable postprocessing and solve some
// rendering problems.
static void draw_buffers(const client::player& localplayer,
                         client::entities_t& entities,
                         client::world::chunks_t& chunks) noexcept {
    const auto make_vbo = [](const auto& vertices) -> GLuint {
        GLuint vbo = 0;
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), std::data(vertices),
                     GL_STATIC_DRAW);
        return vbo;
    };
    const auto make_vao = []() -> GLuint {
        GLuint vao = 0;
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
        // position
        glVertexAttribPointer(0, sizeof(glm::vec2) / sizeof(float), GL_FLOAT,
                              GL_FALSE, sizeof(glm::vec2), nullptr);
        glEnableVertexAttribArray(0);
        return vao;
    };
    const auto make_ebo = [](const auto& indices) -> GLuint {
        GLuint ebo = 0;
        glGenBuffers(1, &ebo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices,
                     GL_STATIC_DRAW);
        return ebo;
    };
    const auto make_fbo = []() -> GLuint {
        GLuint fbo;
        glGenFramebuffers(1, &fbo);
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        return fbo;
    };
    const auto make_texture = [](const auto& internal_format,
                                 const auto& format,
                                 const int texture_offset) -> GLuint {
        GLuint texture;
        glGenTextures(1, &texture);
        glActiveTexture(GL_TEXTURE2 + texture_offset);
        glBindTexture(GL_TEXTURE_2D, texture);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

        const glm::vec2& window = client::render::get_window_size();
        glTexImage2D(GL_TEXTURE_2D, 0, internal_format, window.x, window.y, 0,
                     internal_format, GL_UNSIGNED_BYTE, nullptr);
        glFramebufferTexture2D(GL_FRAMEBUFFER, format, GL_TEXTURE_2D, texture,
                               0);
        return texture;
    };

    static constexpr std::array<glm::vec2, 4> vertices = {
        glm::vec2{-1.0f, -1.0f}, glm::vec2{1.0f, -1.0f}, glm::vec2{1.0f, 1.0f},
        glm::vec2{-1.0f, 1.0f}};
    static constexpr std::array<unsigned, 6> indices = {0, 1, 2, 2, 3, 0};
    static const client::render::program framebuffer_program{
        "res/shaders/framebuffer.vs", "res/shaders/framebuffer.fs"};
    static const GLuint vbo [[maybe_unused]] = make_vbo(vertices);
    static const GLuint vao = make_vao();
    static const GLuint ebo [[maybe_unused]] = make_ebo(indices);
    // Main frame buffer object.
    static const GLuint fbo_main = make_fbo();
    static const GLuint main_colour [[maybe_unused]] =
        make_texture(GL_RGB, GL_COLOR_ATTACHMENT0, 0);
    static const GLuint main_depth [[maybe_unused]] =
        make_texture(GL_DEPTH_COMPONENT, GL_DEPTH_ATTACHMENT, 1);
    // Water frame buffer object.
    static const GLuint fbo_water = make_fbo();
    static const GLuint water_colour [[maybe_unused]] =
        make_texture(GL_RGB, GL_COLOR_ATTACHMENT0, 2);
    static const GLuint water_depth [[maybe_unused]] =
        make_texture(GL_DEPTH_COMPONENT, GL_DEPTH_ATTACHMENT, 3);
    static const GLint u_is_underwater =
        glGetUniformLocation(framebuffer_program, "_u_is_underwater");

    // We render our main scene on the main framebuffer.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_main);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    draw_main_pass(localplayer, entities, chunks);

    // We render our water scene on the water framebuffer.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_water);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    const bool is_underwater = [&]() -> bool {
        const glm::ivec3 pos{client::render::camera::get_pos()};

        const auto find_it = chunks.find(localplayer.get_chunk_pos());
        if (find_it == std::end(chunks) || !find_it->second.has_value()) {
            return false;
        }
        if (shared::world::chunk::is_outside_chunk(pos)) {
            return false;
        }

        const auto block = find_it->second->get_block(pos);
        return block.type == shared::world::block::type::water;
    }();
    if (is_underwater) {
        glFrontFace(GL_CW);
        draw_water_pass(localplayer, chunks);
        glFrontFace(GL_CCW);
    } else {
        draw_water_pass(localplayer, chunks);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, main_colour);
    glDisable(GL_DEPTH_TEST);
    glUseProgram(framebuffer_program);
    glBindVertexArray(vao);

    glUniform1i(u_is_underwater, is_underwater);

    glDrawElements(GL_TRIANGLES, std::size(indices), GL_UNSIGNED_INT, nullptr);

    static client::render::program postprocess_program{
        "res/shaders/framebuffer.vs", "res/shaders/postprocess.fs"};
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glUseProgram(postprocess_program);
    glDrawElements(GL_TRIANGLES, std::size(indices), GL_UNSIGNED_INT, nullptr);

    glEnable(GL_DEPTH_TEST);
}

void draw(entities_t& entities, world::chunks_t& chunks) noexcept {
    const auto& localplayer = get_localplayer();

    update_camera(localplayer);
    client::render::update_uniforms();

    draw_buffers(localplayer, entities, chunks);
    draw_hud(localplayer, chunks);
    draw_wts_text(localplayer, entities);

    client::window::draw();

    client::render::swap_window();
}

} // namespace render
} // namespace client