MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
4#if defined(MXVK_USE_EIGEN_MATH)
6#else
7#include "mxvk/mxvk_math.h"
8#endif
9#include "mxvk/mxvk_png.hpp"
10
11#include <SDL3/SDL.h>
12
13#include <algorithm>
14#include <array>
15#include <chrono>
16#include <cmath>
17#include <cstdint>
18#include <cstdlib>
19#include <format>
20#include <iostream>
21#include <limits>
22#include <memory>
23#include <random>
24#include <string>
25#include <vector>
26
27#ifndef math3d_puzzle_drop_ASSET_DIR
28#define math3d_puzzle_drop_ASSET_DIR "."
29#endif
30
31namespace {
32 constexpr int BOARD_WIDTH = 20;
33 constexpr int BOARD_HEIGHT = 22;
34 constexpr int DEFAULT_FRAME_WIDTH = 640;
35 constexpr int DEFAULT_FRAME_HEIGHT = 480;
36 constexpr int LEVEL_COUNT = 8;
37 constexpr float BLOCK_SPACING = 0.145f;
38 constexpr float BLOCK_HALF_EXTENT = 0.064f;
39 constexpr float FRAME_HALF_EXTENT = BLOCK_HALF_EXTENT * 0.72f;
40 constexpr float FRAME_GAP = 0.016f;
41 constexpr float CAMERA_DISTANCE = 4.1f;
42 constexpr std::array<float, 3> FALL_SECONDS{0.86f, 0.68f, 0.50f};
43 constexpr std::array<const char *, 10> BLOCK_TEXTURE_FILES{
44 "red1.png",
45 "red2.png",
46 "red3.png",
47 "green1.png",
48 "green2.png",
49 "green3.png",
50 "blue1.png",
51 "blue2.png",
52 "blue3.png",
53 "red3.png",
54 };
55
56 class SurfaceDeleter {
57 public:
58 void operator()(SDL_Surface *surface) const {
59 SDL_DestroySurface(surface);
60 }
61 };
62
63 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
64
79
80 enum class ShiftDirection {
83 };
84
85 struct Block {
86 int x = 0;
87 int y = 0;
89 };
90
91 struct Piece {
92 std::array<Block, 3> blocks{};
93 int position = 0;
94
95 void new_piece(int start_x, int start_y, std::mt19937 &rng) {
96 blocks[0] = {start_x, start_y, random_type(rng)};
97 blocks[1] = {start_x, start_y + 1, random_type(rng)};
98 blocks[2] = {start_x, start_y + 2, random_type(rng)};
99 position = 0;
100 }
101
102 void shift(ShiftDirection direction) {
103 const std::array<BlockType, 3> types{blocks[0].type, blocks[1].type, blocks[2].type};
104 if (direction == ShiftDirection::Down) {
105 blocks[0].type = types[2];
106 blocks[1].type = types[0];
107 blocks[2].type = types[1];
108 } else {
109 blocks[0].type = types[1];
110 blocks[1].type = types[2];
111 blocks[2].type = types[0];
112 }
113 }
114
115 void move_left() {
116 for (Block &block : blocks) {
117 --block.x;
118 }
119 }
120
121 void move_right() {
122 for (Block &block : blocks) {
123 ++block.x;
124 }
125 }
126
127 void move_down() {
128 for (Block &block : blocks) {
129 ++block.y;
130 }
131 }
132
133 void rotate_left() {
134 if (position == 0) {
135 blocks[1].y -= 1;
136 blocks[1].x -= 1;
137 blocks[2].x -= 2;
138 blocks[2].y -= 2;
139 position = 1;
140 } else if (position == 1) {
141 blocks[1].y += 1;
142 blocks[1].x += 1;
143 blocks[2].y += 2;
144 blocks[2].x += 2;
145 position = 0;
146 }
147 }
148
150 if (position == 0) {
151 blocks[1].x += 1;
152 blocks[1].y -= 1;
153 blocks[2].x += 2;
154 blocks[2].y -= 2;
155 position = 2;
156 } else if (position == 2) {
157 blocks[1].x -= 1;
158 blocks[1].y += 1;
159 blocks[2].x -= 2;
160 blocks[2].y += 2;
161 position = 0;
162 }
163 }
164
165 private:
166 [[nodiscard]] static BlockType random_type(std::mt19937 &rng) {
167 std::uniform_int_distribution<int> distribution(static_cast<int>(BlockType::Red1), static_cast<int>(BlockType::Match));
168 return static_cast<BlockType>(distribution(rng));
169 }
170 };
171
172 struct Cell {
174 int clear_value = 0;
176 };
177
178 [[nodiscard]] bool is_play_block(BlockType type) {
179 return type >= BlockType::Red1 && type <= BlockType::Match;
180 }
181
182 [[nodiscard]] int texture_index(BlockType type) {
183 return is_play_block(type) ? static_cast<int>(type) - static_cast<int>(BlockType::Red1) : 0;
184 }
185
186 [[nodiscard]] bool same_or_match(BlockType actual, BlockType expected) {
187 return actual == expected || actual == BlockType::Match;
188 }
189
191 int width = 0;
192 int height = 0;
193 std::vector<mxvk::MXCOLOR> pixels;
194 };
195
196 struct Texture {
197 int width = 0;
198 int height = 0;
199 std::vector<mxvk::MXCOLOR> pixels;
200 std::vector<TextureLevel> mipmaps;
201
202 [[nodiscard]] mxvk::MXCOLOR sample_filtered(float u, float v, float lod) const {
203 const float clamped_lod = std::clamp(lod, 0.0f, static_cast<float>(mipmaps.size()));
204 const int first_level = static_cast<int>(std::floor(clamped_lod));
205 const int second_level = std::min(first_level + 1, static_cast<int>(mipmaps.size()));
206 const float blend = clamped_lod - static_cast<float>(first_level);
207 const mxvk::MXCOLOR first = sample_bilinear(first_level, u, v);
208 const mxvk::MXCOLOR second = sample_bilinear(second_level, u, v);
209 return blend_color(first, second, blend);
210 }
211
212 private:
213 [[nodiscard]] mxvk::MXCOLOR sample_bilinear(int level, float u, float v) const {
214 const int level_width = level == 0 ? width : mipmaps[static_cast<std::size_t>(level - 1)].width;
215 const int level_height = level == 0 ? height : mipmaps[static_cast<std::size_t>(level - 1)].height;
216 const std::vector<mxvk::MXCOLOR> &level_pixels = level == 0 ? pixels : mipmaps[static_cast<std::size_t>(level - 1)].pixels;
217 const float source_x = std::clamp(u, 0.0f, 1.0f) * static_cast<float>(level_width - 1);
218 const float source_y = std::clamp(v, 0.0f, 1.0f) * static_cast<float>(level_height - 1);
219 const int x0 = static_cast<int>(std::floor(source_x));
220 const int y0 = static_cast<int>(std::floor(source_y));
221 const int x1 = std::min(x0 + 1, level_width - 1);
222 const int y1 = std::min(y0 + 1, level_height - 1);
223 const float x_blend = source_x - static_cast<float>(x0);
224 const float y_blend = source_y - static_cast<float>(y0);
225 const mxvk::MXCOLOR top = blend_color(
226 level_pixels[static_cast<std::size_t>(y0 * level_width + x0)],
227 level_pixels[static_cast<std::size_t>(y0 * level_width + x1)],
228 x_blend);
229 const mxvk::MXCOLOR bottom = blend_color(
230 level_pixels[static_cast<std::size_t>(y1 * level_width + x0)],
231 level_pixels[static_cast<std::size_t>(y1 * level_width + x1)],
232 x_blend);
233 return blend_color(top, bottom, y_blend);
234 }
235
236 [[nodiscard]] static mxvk::MXCOLOR blend_color(mxvk::MXCOLOR first, mxvk::MXCOLOR second, float amount) {
237 const auto blend_channel = [amount](std::uint8_t left, std::uint8_t right) {
238 return static_cast<std::uint8_t>(
239 std::clamp(
240 static_cast<float>(left) + (static_cast<float>(right) - static_cast<float>(left)) * amount,
241 0.0f,
242 255.0f) +
243 0.5f);
244 };
245 const std::uint8_t red = blend_channel(mxvk::color_r(first), mxvk::color_r(second));
246 const std::uint8_t green = blend_channel(mxvk::color_g(first), mxvk::color_g(second));
247 const std::uint8_t blue = blend_channel(mxvk::color_b(first), mxvk::color_b(second));
248 const std::uint8_t alpha = blend_channel(mxvk::color_a(first), mxvk::color_a(second));
249 return (static_cast<mxvk::MXCOLOR>(alpha) << 24U) |
250 (static_cast<mxvk::MXCOLOR>(red) << 16U) |
251 (static_cast<mxvk::MXCOLOR>(green) << 8U) |
252 static_cast<mxvk::MXCOLOR>(blue);
253 }
254 };
255
256 void build_mipmaps(Texture &texture) {
257 int source_width = texture.width;
258 int source_height = texture.height;
259 const std::vector<mxvk::MXCOLOR> *source_pixels = &texture.pixels;
260 while (source_width > 1 || source_height > 1) {
261 TextureLevel level;
262 level.width = std::max(1, source_width / 2);
263 level.height = std::max(1, source_height / 2);
264 level.pixels.resize(static_cast<std::size_t>(level.width * level.height));
265 for (int y = 0; y < level.height; ++y) {
266 for (int x = 0; x < level.width; ++x) {
267 std::uint32_t red = 0;
268 std::uint32_t green = 0;
269 std::uint32_t blue = 0;
270 std::uint32_t alpha = 0;
271 for (int offset_y = 0; offset_y < 2; ++offset_y) {
272 for (int offset_x = 0; offset_x < 2; ++offset_x) {
273 const int source_x = std::min(x * 2 + offset_x, source_width - 1);
274 const int source_y = std::min(y * 2 + offset_y, source_height - 1);
275 const mxvk::MXCOLOR color = (*source_pixels)[static_cast<std::size_t>(source_y * source_width + source_x)];
276 red += mxvk::color_r(color);
277 green += mxvk::color_g(color);
278 blue += mxvk::color_b(color);
279 alpha += mxvk::color_a(color);
280 }
281 }
282 level.pixels[static_cast<std::size_t>(y * level.width + x)] =
283 ((alpha / 4U) << 24U) |
284 ((red / 4U) << 16U) |
285 ((green / 4U) << 8U) |
286 (blue / 4U);
287 }
288 }
289 texture.mipmaps.push_back(std::move(level));
290 source_width = texture.mipmaps.back().width;
291 source_height = texture.mipmaps.back().height;
292 source_pixels = &texture.mipmaps.back().pixels;
293 }
294 }
295
296 [[nodiscard]] Texture load_texture(const std::string &path, bool generate_mipmaps = false) {
297 SurfacePtr loaded(mxvk::LoadPNG(path.c_str()));
298 if (!loaded) {
299 throw mxvk::Exception(std::format("3dmath_puzzle_drop: failed to load PNG '{}'", path));
300 }
301 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
302 if (!rgba) {
303 throw mxvk::Exception(std::format("3dmath_puzzle_drop: failed to convert PNG '{}': {}", path, SDL_GetError()));
304 }
305 const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(rgba->format);
306 if (format == nullptr) {
307 throw mxvk::Exception(std::format("3dmath_puzzle_drop: failed to query PNG format '{}'", path));
308 }
309
310 Texture texture;
311 texture.width = rgba->w;
312 texture.height = rgba->h;
313 texture.pixels.resize(static_cast<std::size_t>(texture.width * texture.height));
314 for (int y = 0; y < texture.height; ++y) {
315 const auto *row = static_cast<const std::uint8_t *>(rgba->pixels) + static_cast<std::size_t>(y * rgba->pitch);
316 const auto *source = reinterpret_cast<const std::uint32_t *>(row);
317 for (int x = 0; x < texture.width; ++x) {
318 std::uint8_t red = 0;
319 std::uint8_t green = 0;
320 std::uint8_t blue = 0;
321 std::uint8_t alpha = 0;
322 SDL_GetRGBA(source[x], format, nullptr, &red, &green, &blue, &alpha);
323 texture.pixels[static_cast<std::size_t>(y * texture.width + x)] =
324 (static_cast<mxvk::MXCOLOR>(alpha) << 24U) |
325 (static_cast<mxvk::MXCOLOR>(red) << 16U) |
326 (static_cast<mxvk::MXCOLOR>(green) << 8U) |
327 static_cast<mxvk::MXCOLOR>(blue);
328 }
329 }
330 if (generate_mipmaps) {
331 build_mipmaps(texture);
332 }
333 return texture;
334 }
335
340
341 class SoftwareRenderer {
342 public:
343 SoftwareRenderer(int width, int height, const std::string &data_root, bool enable_warp_fix, bool enable_mipmapping, float mip_bias)
344 : frame_width(width),
345 frame_height(height),
346 depth_buffer(static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * MSAA_SAMPLE_COUNT),
347 color_buffer(static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * MSAA_SAMPLE_COUNT),
348 background(load_texture(data_root + "/level1.png")),
349 intro(load_texture(data_root + "/intro1.png")),
350 warp_fix_enabled(enable_warp_fix),
351 mipmapping_enabled(enable_mipmapping),
352 mip_level_bias(mip_bias) {
353 frame_surface.reset(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
354 if (!frame_surface) {
355 throw mxvk::Exception(std::format("3dmath_puzzle_drop: failed to create framebuffer: {}", SDL_GetError()));
356 }
357 for (const char *filename : BLOCK_TEXTURE_FILES) {
358 block_textures.push_back(load_texture(data_root + "/" + filename, mipmapping_enabled));
359 }
360 }
361
362 [[nodiscard]] SDL_Surface *surface() const {
363 return frame_surface.get();
364 }
365
366 [[nodiscard]] int width() const {
367 return frame_width;
368 }
369
370 [[nodiscard]] int height() const {
371 return frame_height;
372 }
373
374 void set_view(float yaw, float pitch, float distance) {
375 camera_rotation.BuildXYZ(pitch, yaw, 0.0f);
376 camera_distance = distance;
377 }
378
379 void begin_frame(bool show_intro) {
380 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
381 draw_flat_image(show_intro ? intro : background);
382 if (!show_intro) {
383 fill_translucent_rectangle(0, 0, frame_width, frame_height, mxvk::MXVK_RGB(3, 8, 16), 150);
384 }
385 }
386
388 for (int y = 0; y < frame_height; ++y) {
389 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + static_cast<std::size_t>(y * frame_surface->pitch);
390 for (int x = 0; x < frame_width; ++x) {
391 auto *pixel = row + static_cast<std::size_t>(x * 4);
392 const mxvk::MXCOLOR background_color =
393 (0xFFU << 24U) |
394 (static_cast<mxvk::MXCOLOR>(pixel[0]) << 16U) |
395 (static_cast<mxvk::MXCOLOR>(pixel[1]) << 8U) |
396 static_cast<mxvk::MXCOLOR>(pixel[2]);
397 const std::size_t pixel_index = static_cast<std::size_t>(y * frame_width + x);
398 const std::size_t first_sample = pixel_index * MSAA_SAMPLE_COUNT;
399 std::uint32_t red = 0;
400 std::uint32_t green = 0;
401 std::uint32_t blue = 0;
402 for (std::size_t sample = 0; sample < MSAA_SAMPLE_COUNT; ++sample) {
403 const std::size_t sample_index = first_sample + sample;
404 const mxvk::MXCOLOR color = std::isfinite(depth_buffer[sample_index])
405 ? color_buffer[sample_index]
406 : background_color;
407 red += mxvk::color_r(color);
408 green += mxvk::color_g(color);
409 blue += mxvk::color_b(color);
410 }
411 pixel[0] = static_cast<std::uint8_t>((red + MSAA_SAMPLE_COUNT / 2U) / MSAA_SAMPLE_COUNT);
412 pixel[1] = static_cast<std::uint8_t>((green + MSAA_SAMPLE_COUNT / 2U) / MSAA_SAMPLE_COUNT);
413 pixel[2] = static_cast<std::uint8_t>((blue + MSAA_SAMPLE_COUNT / 2U) / MSAA_SAMPLE_COUNT);
414 pixel[3] = 255;
415 }
416 }
417 }
418
419 void draw_block(BlockType type, float x, float y, float z, float half_extent, const mxvk::vec4D &tint) {
420 draw_cube(&block_textures[static_cast<std::size_t>(texture_index(type))], x, y, z, half_extent, tint);
421 }
422
423 void draw_wildcard(float x, float y, float z, float half_extent, const mxvk::vec4D &color) {
424 mxvk::vec4D neon(
425 std::max(color.x, 0.08f),
426 std::max(color.y, 0.08f),
427 std::max(color.z, 0.08f),
428 1.0f);
429 const float brightest_channel = std::max({neon.x, neon.y, neon.z});
430 neon.x /= brightest_channel;
431 neon.y /= brightest_channel;
432 neon.z /= brightest_channel;
433 draw_cube(nullptr, x, y, z, half_extent, neon, true);
434 }
435
436 void draw_solid_cube(float x, float y, float z, float half_extent, mxvk::MXCOLOR color) {
437 const mxvk::vec4D tint(
438 static_cast<float>(mxvk::color_r(color)) / 255.0f,
439 static_cast<float>(mxvk::color_g(color)) / 255.0f,
440 static_cast<float>(mxvk::color_b(color)) / 255.0f,
441 1.0f);
442 draw_cube(nullptr, x, y, z, half_extent, tint);
443 }
444
445 void draw_rectangle(int left, int top, int width, int height, mxvk::MXCOLOR color) {
446 const int first_x = std::clamp(left, 0, frame_width);
447 const int first_y = std::clamp(top, 0, frame_height);
448 const int last_x = std::clamp(left + width, 0, frame_width);
449 const int last_y = std::clamp(top + height, 0, frame_height);
450 for (int y = first_y; y < last_y; ++y) {
451 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + static_cast<std::size_t>(y * frame_surface->pitch);
452 for (int x = first_x; x < last_x; ++x) {
453 write_pixel(row + static_cast<std::size_t>(x * 4), color);
454 }
455 }
456 }
457
458 void draw_block_image(BlockType type, int left, int top, int width, int height) {
459 if (!is_play_block(type) || width <= 0 || height <= 0) {
460 return;
461 }
462
463 const Texture &texture = block_textures[static_cast<std::size_t>(texture_index(type))];
464 const int first_x = std::clamp(left, 0, frame_width);
465 const int first_y = std::clamp(top, 0, frame_height);
466 const int last_x = std::clamp(left + width, 0, frame_width);
467 const int last_y = std::clamp(top + height, 0, frame_height);
468 for (int y = first_y; y < last_y; ++y) {
469 const int source_y = std::clamp((y - top) * texture.height / height, 0, texture.height - 1);
470 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + static_cast<std::size_t>(y * frame_surface->pitch);
471 for (int x = first_x; x < last_x; ++x) {
472 const int source_x = std::clamp((x - left) * texture.width / width, 0, texture.width - 1);
473 const mxvk::MXCOLOR color = texture.pixels[static_cast<std::size_t>(source_y * texture.width + source_x)];
474 blend_pixel(row + static_cast<std::size_t>(x * 4), color);
475 }
476 }
477 }
478
479 void draw_text(TTF_Font *font, const std::string &text, int x, int y, const SDL_Color &color) {
480 if (font == nullptr || text.empty()) {
481 return;
482 }
483
484 SurfacePtr text_surface(TTF_RenderText_Blended(font, text.c_str(), 0, color));
485 if (!text_surface) {
486 return;
487 }
488 SDL_SetSurfaceBlendMode(text_surface.get(), SDL_BLENDMODE_BLEND);
489 const SDL_Rect destination{x, y, text_surface->w, text_surface->h};
490 SDL_BlitSurface(text_surface.get(), nullptr, frame_surface.get(), &destination);
491 }
492
493 private:
494 SurfacePtr frame_surface;
495 int frame_width = 0;
496 int frame_height = 0;
497 std::vector<float> depth_buffer;
498 std::vector<mxvk::MXCOLOR> color_buffer;
499 Texture background;
500 Texture intro;
501 std::vector<Texture> block_textures;
502 bool warp_fix_enabled = true;
503 bool mipmapping_enabled = true;
504 float mip_level_bias = 0.0f;
505 mxvk::Mat4D camera_rotation;
506 float camera_distance = CAMERA_DISTANCE;
507
508 static constexpr std::size_t MSAA_SAMPLE_COUNT = 4;
509 static constexpr std::array<std::array<float, 2>, MSAA_SAMPLE_COUNT> MSAA_SAMPLE_OFFSETS{{
510 {{0.375f, 0.125f}},
511 {{0.875f, 0.375f}},
512 {{0.125f, 0.625f}},
513 {{0.625f, 0.875f}},
514 }};
515
516 static constexpr std::array<mxvk::vec4D, 8> CUBE_VERTICES{{
517 {-1.0f, -1.0f, -1.0f, 1.0f},
518 {1.0f, -1.0f, -1.0f, 1.0f},
519 {1.0f, 1.0f, -1.0f, 1.0f},
520 {-1.0f, 1.0f, -1.0f, 1.0f},
521 {-1.0f, -1.0f, 1.0f, 1.0f},
522 {1.0f, -1.0f, 1.0f, 1.0f},
523 {1.0f, 1.0f, 1.0f, 1.0f},
524 {-1.0f, 1.0f, 1.0f, 1.0f},
525 }};
526
527 static constexpr std::array<std::array<int, 4>, 6> CUBE_FACES{{
528 {0, 3, 2, 1},
529 {4, 5, 6, 7},
530 {0, 4, 7, 3},
531 {1, 2, 6, 5},
532 {3, 7, 6, 2},
533 {0, 1, 5, 4},
534 }};
535
536 static const std::array<std::array<mxvk::vec2D, 4>, 6> CUBE_FACE_UVS;
537
538 static void write_pixel(std::uint8_t *pixel, mxvk::MXCOLOR color) {
539 pixel[0] = mxvk::color_r(color);
540 pixel[1] = mxvk::color_g(color);
541 pixel[2] = mxvk::color_b(color);
542 pixel[3] = mxvk::color_a(color);
543 }
544
545 static void blend_pixel(std::uint8_t *pixel, mxvk::MXCOLOR color) {
546 const int alpha = mxvk::color_a(color);
547 const int inverse_alpha = 255 - alpha;
548 pixel[0] = static_cast<std::uint8_t>((mxvk::color_r(color) * alpha + pixel[0] * inverse_alpha) / 255);
549 pixel[1] = static_cast<std::uint8_t>((mxvk::color_g(color) * alpha + pixel[1] * inverse_alpha) / 255);
550 pixel[2] = static_cast<std::uint8_t>((mxvk::color_b(color) * alpha + pixel[2] * inverse_alpha) / 255);
551 pixel[3] = 255;
552 }
553
554 void draw_flat_image(const Texture &texture) {
555 for (int y = 0; y < frame_height; ++y) {
556 const int source_y = y * texture.height / frame_height;
557 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + static_cast<std::size_t>(y * frame_surface->pitch);
558 for (int x = 0; x < frame_width; ++x) {
559 const int source_x = x * texture.width / frame_width;
560 const mxvk::MXCOLOR color = texture.pixels[static_cast<std::size_t>(source_y * texture.width + source_x)];
561 write_pixel(row + static_cast<std::size_t>(x * 4), color | 0xFF000000U);
562 }
563 }
564 }
565
566 void fill_translucent_rectangle(int left, int top, int width, int height, mxvk::MXCOLOR color, std::uint8_t alpha) {
567 const int inverse_alpha = 255 - alpha;
568 for (int y = top; y < top + height; ++y) {
569 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + static_cast<std::size_t>(y * frame_surface->pitch);
570 for (int x = left; x < left + width; ++x) {
571 auto *pixel = row + static_cast<std::size_t>(x * 4);
572 pixel[0] = static_cast<std::uint8_t>((pixel[0] * inverse_alpha + mxvk::color_r(color) * alpha) / 255);
573 pixel[1] = static_cast<std::uint8_t>((pixel[1] * inverse_alpha + mxvk::color_g(color) * alpha) / 255);
574 pixel[2] = static_cast<std::uint8_t>((pixel[2] * inverse_alpha + mxvk::color_b(color) * alpha) / 255);
575 }
576 }
577 }
578
579 [[nodiscard]] mxvk::vec4D project(const mxvk::vec4D &point) const {
580 const float scale = static_cast<float>(std::min(frame_width, frame_height)) * 0.71f;
581 const float z = std::max(point.z, 0.001f);
582 return {
583 static_cast<float>(frame_width) * 0.43f + point.x / z * scale,
584 static_cast<float>(frame_height) * 0.50f - point.y / z * scale,
585 point.z,
586 1.0f,
587 };
588 }
589
590 void draw_cube(const Texture *texture, float x, float y, float z, float half_extent, const mxvk::vec4D &tint, bool neon = false) {
591 std::array<mxvk::vec4D, 8> camera_vertices{};
592 std::array<mxvk::vec4D, 8> projected{};
593 for (std::size_t index = 0; index < CUBE_VERTICES.size(); ++index) {
594 mxvk::vec4D point(
595 CUBE_VERTICES[index].x * half_extent + x,
596 CUBE_VERTICES[index].y * half_extent + y,
597 CUBE_VERTICES[index].z * half_extent + z,
598 1.0f);
599 point = camera_rotation.MulVec(point);
600 point.z += camera_distance;
601 camera_vertices[index] = point;
602 projected[index] = project(point);
603 }
604
605 const mxvk::vec4D light_direction(-0.35f, 0.65f, -1.0f, 0.0f);
606 for (std::size_t face_index = 0; face_index < CUBE_FACES.size(); ++face_index) {
607 const auto &face = CUBE_FACES[face_index];
608 const auto &face_uvs = CUBE_FACE_UVS[face_index];
609 const mxvk::vec4D &a = camera_vertices[static_cast<std::size_t>(face[0])];
610 const mxvk::vec4D &b = camera_vertices[static_cast<std::size_t>(face[1])];
611 const mxvk::vec4D &c = camera_vertices[static_cast<std::size_t>(face[2])];
612 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
613 normal.Normalize();
614 const mxvk::vec4D center = (a + b + c + camera_vertices[static_cast<std::size_t>(face[3])]) * 0.25f;
615 if (normal.DotProduct({-center.x, -center.y, -center.z, 0.0f}) <= 0.0f) {
616 continue;
617 }
618 mxvk::vec4D normalized_light = light_direction;
619 normalized_light.Normalize();
620 float intensity = std::clamp(0.40f + std::max(0.0f, normal.DotProduct(normalized_light)) * 0.60f, 0.0f, 1.0f);
621 if (neon) {
622 mxvk::vec4D key_light(-0.18f, 0.58f, -0.80f, 0.0f);
623 mxvk::vec4D fill_light(0.12f, 0.08f, -0.99f, 0.0f);
624 mxvk::vec4D view_direction(-center.x, -center.y, -center.z, 0.0f);
625 key_light.Normalize();
626 fill_light.Normalize();
627 view_direction.Normalize();
628 const float key_diffuse = std::max(normal.DotProduct(key_light), 0.0f);
629 const float fill_diffuse = std::max(normal.DotProduct(fill_light), 0.0f);
630 const float diffuse = std::min(key_diffuse * 0.50f + fill_diffuse * 0.62f, 1.0f);
631 const float rim_amount = 1.0f - std::max(normal.DotProduct(view_direction), 0.0f);
632 const float rim_fraction = std::clamp((rim_amount - 0.12f) / 0.88f, 0.0f, 1.0f);
633 const float neon_rim = rim_fraction * rim_fraction * (3.0f - 2.0f * rim_fraction);
634 intensity = 0.50f + diffuse * 0.52f + neon_rim * 0.34f + 0.12f;
635 }
636 const RasterVertex vertex_a{projected[static_cast<std::size_t>(face[0])], face_uvs[0]};
637 const RasterVertex vertex_b{projected[static_cast<std::size_t>(face[1])], face_uvs[1]};
638 const RasterVertex vertex_c{projected[static_cast<std::size_t>(face[2])], face_uvs[2]};
639 const RasterVertex vertex_d{projected[static_cast<std::size_t>(face[3])], face_uvs[3]};
640 rasterize_triangle(vertex_a, vertex_b, vertex_c, texture, tint, intensity);
641 rasterize_triangle(vertex_a, vertex_c, vertex_d, texture, tint, intensity);
642 }
643 }
644
645 void rasterize_triangle(const RasterVertex &a,
646 const RasterVertex &b,
647 const RasterVertex &c,
648 const Texture *texture,
649 const mxvk::vec4D &tint,
650 float intensity) {
651 const mxvk::vec2D p0(a.position.x, a.position.y);
652 const mxvk::vec2D p1(b.position.x, b.position.y);
653 const mxvk::vec2D p2(c.position.x, c.position.y);
654 const float area = mxvk::edge_function(p0, p1, p2);
655 if (std::fabs(area) <= mxvk::EPSILON) {
656 return;
657 }
658 const int min_x = std::max(0, static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x}))));
659 const int max_x = std::min(frame_width - 1, static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x}))));
660 const int min_y = std::max(0, static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y}))));
661 const int max_y = std::min(frame_height - 1, static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y}))));
662 const float inverse_area = 1.0f / area;
663 const float inverse_z0 = 1.0f / a.position.z;
664 const float inverse_z1 = 1.0f / b.position.z;
665 const float inverse_z2 = 1.0f / c.position.z;
666 float texture_lod = 0.0f;
667 if (texture != nullptr && mipmapping_enabled) {
668 const auto texels_per_pixel = [texture](const RasterVertex &first, const RasterVertex &second) {
669 const float screen_width = second.position.x - first.position.x;
670 const float screen_height = second.position.y - first.position.y;
671 const float screen_distance = std::max(std::hypot(screen_width, screen_height), 0.001f);
672 const float texture_width = (second.uv.x - first.uv.x) * static_cast<float>(texture->width);
673 const float texture_height = (second.uv.y - first.uv.y) * static_cast<float>(texture->height);
674 return std::hypot(texture_width, texture_height) / screen_distance;
675 };
676 const float minification = std::max({
677 texels_per_pixel(a, b),
678 texels_per_pixel(b, c),
679 texels_per_pixel(c, a),
680 1.0f,
681 });
682 texture_lod = std::max(0.0f, std::log2(minification) + mip_level_bias);
683 }
684
685 for (int y = min_y; y <= max_y; ++y) {
686 for (int x = min_x; x <= max_x; ++x) {
687 const std::size_t pixel_index = static_cast<std::size_t>(y * frame_width + x);
688 const std::size_t first_sample = pixel_index * MSAA_SAMPLE_COUNT;
689 std::array<float, MSAA_SAMPLE_COUNT> sample_depths{};
690 std::uint8_t passing_samples = 0;
691 float centroid_x = 0.0f;
692 float centroid_y = 0.0f;
693 int passing_sample_count = 0;
694 for (std::size_t sample = 0; sample < MSAA_SAMPLE_COUNT; ++sample) {
695 const mxvk::vec2D point(
696 static_cast<float>(x) + MSAA_SAMPLE_OFFSETS[sample][0],
697 static_cast<float>(y) + MSAA_SAMPLE_OFFSETS[sample][1]);
698 const float edge0 = mxvk::edge_function(p1, p2, point);
699 const float edge1 = mxvk::edge_function(p2, p0, point);
700 const float edge2 = mxvk::edge_function(p0, p1, point);
701 if ((area > 0.0f && (edge0 < 0.0f || edge1 < 0.0f || edge2 < 0.0f)) ||
702 (area < 0.0f && (edge0 > 0.0f || edge1 > 0.0f || edge2 > 0.0f))) {
703 continue;
704 }
705 const float weight0 = edge0 * inverse_area;
706 const float weight1 = edge1 * inverse_area;
707 const float weight2 = edge2 * inverse_area;
708 const float inverse_z = weight0 * inverse_z0 + weight1 * inverse_z1 + weight2 * inverse_z2;
709 const float depth = 1.0f / inverse_z;
710 const std::size_t sample_index = first_sample + sample;
711 if (depth >= depth_buffer[sample_index]) {
712 continue;
713 }
714 sample_depths[sample] = depth;
715 passing_samples |= static_cast<std::uint8_t>(1U << sample);
716 centroid_x += point.x;
717 centroid_y += point.y;
718 ++passing_sample_count;
719 }
720 if (passing_samples == 0) {
721 continue;
722 }
723
724 const mxvk::vec2D shading_point(
725 centroid_x / static_cast<float>(passing_sample_count),
726 centroid_y / static_cast<float>(passing_sample_count));
727 const float edge0 = mxvk::edge_function(p1, p2, shading_point);
728 const float edge1 = mxvk::edge_function(p2, p0, shading_point);
729 const float edge2 = mxvk::edge_function(p0, p1, shading_point);
730 const float weight0 = edge0 * inverse_area;
731 const float weight1 = edge1 * inverse_area;
732 const float weight2 = edge2 * inverse_area;
733 const float inverse_z = weight0 * inverse_z0 + weight1 * inverse_z1 + weight2 * inverse_z2;
734 mxvk::MXCOLOR color = mxvk::MXVK_RGB(255, 255, 255);
735 if (texture != nullptr) {
736 const float texture_weight0 = warp_fix_enabled ? weight0 * inverse_z0 / inverse_z : weight0;
737 const float texture_weight1 = warp_fix_enabled ? weight1 * inverse_z1 / inverse_z : weight1;
738 const float texture_weight2 = warp_fix_enabled ? weight2 * inverse_z2 / inverse_z : weight2;
739 const float u =
740 texture_weight0 * a.uv.x +
741 texture_weight1 * b.uv.x +
742 texture_weight2 * c.uv.x;
743 const float v =
744 texture_weight0 * a.uv.y +
745 texture_weight1 * b.uv.y +
746 texture_weight2 * c.uv.y;
747 color = texture->sample_filtered(u, v, texture_lod);
748 }
749 const mxvk::MXCOLOR shaded_color =
750 (0xFFU << 24U) |
751 (static_cast<mxvk::MXCOLOR>(std::clamp(static_cast<float>(mxvk::color_r(color)) * tint.x * intensity, 0.0f, 255.0f)) << 16U) |
752 (static_cast<mxvk::MXCOLOR>(std::clamp(static_cast<float>(mxvk::color_g(color)) * tint.y * intensity, 0.0f, 255.0f)) << 8U) |
753 static_cast<mxvk::MXCOLOR>(std::clamp(static_cast<float>(mxvk::color_b(color)) * tint.z * intensity, 0.0f, 255.0f));
754 for (std::size_t sample = 0; sample < MSAA_SAMPLE_COUNT; ++sample) {
755 if ((passing_samples & static_cast<std::uint8_t>(1U << sample)) == 0) {
756 continue;
757 }
758 const std::size_t sample_index = first_sample + sample;
759 depth_buffer[sample_index] = sample_depths[sample];
760 color_buffer[sample_index] = shaded_color;
761 }
762 }
763 }
764 }
765 };
766
767 const std::array<std::array<mxvk::vec2D, 4>, 6> SoftwareRenderer::CUBE_FACE_UVS{{
768 {{{0.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}}},
769 {{{0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}}},
770 {{{0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}}},
771 {{{0.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}}},
772 {{{0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}}},
773 {{{0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}}},
774 }};
775
776 class PuzzleDropWindow final : public mxvk::VK_Window {
777 public:
778 PuzzleDropWindow(const Arguments &args, const FramebufferDimensions &framebuffer)
779 : mxvk::VK_Window("MXVK 3D Math Puzzle Drop", args.width, args.height, args.fullscreen, MXVK_VALIDATION, args.enable_vsync),
780 data_root(((args.path.empty() || args.path == ".") ? std::string(math3d_puzzle_drop_ASSET_DIR) : args.path) + "/data"),
781 renderer(framebuffer.width, framebuffer.height, data_root, !args.nowarpfix, !args.disable_mipmap, args.mip_bias),
782 ui_font(data_root + "/font.ttf", std::max(8, static_cast<int>(std::round(22.0f * framebuffer_scale(framebuffer))))) {
783 setClearColor(0.01f, 0.02f, 0.03f, 1.0f);
785 std::random_device random_device;
786 rng.seed(random_device());
787 try_open_first_gamepad();
788 reset_game();
789 intro_start = std::chrono::steady_clock::now();
790 }
791
792 ~PuzzleDropWindow() override {
793 close_gamepad();
794 }
795
796 void event(SDL_Event &event) override {
797 if (event.type == SDL_EVENT_QUIT) {
798 exit();
799 return;
800 }
801
802 if (event.type == SDL_EVENT_GAMEPAD_ADDED) {
803 if (!open_gamepad(event.gdevice.which)) {
804 try_open_first_gamepad();
805 }
806 return;
807 }
808
809 if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
810 if (gamepad != nullptr && event.gdevice.which == gamepad_id) {
811 close_gamepad();
812 try_open_first_gamepad();
813 }
814 return;
815 }
816
817 if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
818 handle_gamepad_button_down(event.gbutton.button);
819 return;
820 }
821
822 if (event.type != SDL_EVENT_KEY_DOWN || event.key.repeat) {
823 return;
824 }
825 if (intro_active && (event.key.key == SDLK_SPACE || event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER)) {
826 finish_intro();
827 return;
828 }
829 switch (event.key.key) {
830 case SDLK_ESCAPE:
831 exit();
832 break;
833 case SDLK_RETURN:
834 case SDLK_KP_ENTER:
835 if (game_over) {
836 reset_game();
837 game_started = true;
838 }
839 break;
840 case SDLK_1:
841 case SDLK_2:
842 case SDLK_3:
843 difficulty = static_cast<int>(event.key.key - SDLK_1);
844 reset_game();
845 game_started = true;
846 break;
847 case SDLK_Z:
848 rotate_left();
849 break;
850 case SDLK_X:
851 rotate_right();
852 break;
853 default:
854 break;
855 }
856 }
857
858 void proc() override {
859 const auto now = std::chrono::steady_clock::now();
860 const float delta_seconds = std::chrono::duration<float>(now - last_input_update).count();
861 last_input_update = now;
862 try_open_first_gamepad();
863 randomize_wildcard_color();
864 if (intro_active && std::chrono::duration<float>(now - intro_start).count() >= 3.5f) {
865 finish_intro();
866 }
867
868 if (!intro_active) {
869 const bool *keys = SDL_GetKeyboardState(nullptr);
870 if (keys != nullptr) {
871 handle_view_controls(keys, delta_seconds);
872 handle_piece_controls(keys, delta_seconds);
873 }
874 handle_gamepad_input(delta_seconds);
875 }
876
877 if (game_started && !game_over) {
878 if (std::chrono::duration<float>(now - last_fall).count() >= FALL_SECONDS[static_cast<std::size_t>(difficulty)]) {
879 key_down();
880 last_fall = now;
881 }
882 if (std::chrono::duration<float>(now - last_process).count() >= 0.018f) {
883 proc_blocks();
884 proc_move_down();
885 last_process = now;
886 }
887 }
888
889 draw_scene();
890 draw_interface();
891 ensure_frame_sprite();
892 frame_sprite->updateTexture(renderer.surface());
893 const int output_width = swapchain_extent.width > 0U ? static_cast<int>(swapchain_extent.width) : 1280;
894 const int output_height = swapchain_extent.height > 0U ? static_cast<int>(swapchain_extent.height) : 720;
895 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
896 }
897
898 private:
899 std::string data_root;
900 SoftwareRenderer renderer;
901 mxvk::Font ui_font;
902 mxvk::VK_Sprite *frame_sprite = nullptr;
903 std::mt19937 rng{};
904 std::array<std::array<Cell, BOARD_WIDTH>, BOARD_HEIGHT> board{};
905 Piece piece{};
906 Piece next_piece{};
907 SDL_Gamepad *gamepad = nullptr;
908 SDL_JoystickID gamepad_id = 0;
909 std::chrono::steady_clock::time_point intro_start{std::chrono::steady_clock::now()};
910 std::chrono::steady_clock::time_point last_fall{std::chrono::steady_clock::now()};
911 std::chrono::steady_clock::time_point last_process{std::chrono::steady_clock::now()};
912 std::chrono::steady_clock::time_point last_input_update{std::chrono::steady_clock::now()};
913 float horizontal_move_timer = 0.0f;
914 float soft_drop_timer = 0.0f;
915 float cycle_timer = 0.0f;
916 float gamepad_move_repeat_timer = 0.0f;
917 float gamepad_soft_drop_repeat_timer = 0.0f;
918 float gamepad_cycle_repeat_timer = 0.0f;
919 float gamepad_move_held_seconds = 0.0f;
920 int horizontal_move_direction = 0;
921 int gamepad_move_direction = 0;
922 bool soft_drop_held = false;
923 bool cycle_held = false;
924 bool gamepad_soft_drop_held = false;
925 bool gamepad_cycle_held = false;
926 int difficulty = 0;
927 int level = 1;
928 int lines = 0;
929 bool intro_active = true;
930 bool game_started = false;
931 bool game_over = false;
932 float grid_yaw = -10.0f;
933 float grid_pitch = -8.0f;
934 float camera_distance = CAMERA_DISTANCE;
935 mxvk::vec4D wildcard_color{1.0f, 0.0f, 1.0f, 1.0f};
936 static constexpr Sint16 GAMEPAD_DEADZONE = 10000;
937 static constexpr float GAMEPAD_MOVE_INITIAL_DELAY_SECONDS = 0.22f;
938 static constexpr float GAMEPAD_MOVE_REPEAT_SECONDS = 0.12f;
939 static constexpr float GAMEPAD_SOFT_DROP_INITIAL_DELAY_SECONDS = 0.18f;
940 static constexpr float GAMEPAD_SOFT_DROP_REPEAT_SECONDS = 0.08f;
941 static constexpr float GAMEPAD_CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
942 static constexpr float GAMEPAD_CYCLE_REPEAT_SECONDS = 0.11f;
943 static constexpr float GAMEPAD_STICK_ROTATE_SPEED = 120.0f;
944 static constexpr float GAMEPAD_STICK_PITCH_SPEED = 100.0f;
945 static constexpr float GAMEPAD_STICK_SCALE = 1.0f / 32768.0f;
946
947 [[nodiscard]] static float framebuffer_scale(const FramebufferDimensions &framebuffer) {
948 return std::min(
949 static_cast<float>(framebuffer.width) / static_cast<float>(DEFAULT_FRAME_WIDTH),
950 static_cast<float>(framebuffer.height) / static_cast<float>(DEFAULT_FRAME_HEIGHT));
951 }
952
953 [[nodiscard]] int scaled(int value) const {
954 return std::max(1, static_cast<int>(std::round(static_cast<float>(value) * framebuffer_scale({renderer.width(), renderer.height()}))));
955 }
956
957 void ensure_frame_sprite() {
958 if (frame_sprite != nullptr) {
959 return;
960 }
961
962 frame_sprite = createSprite(renderer.surface());
963 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
964 }
965
966 void draw_interface() {
967 const SDL_Color primary{255, 244, 223, 255};
968 if (intro_active) {
969 renderer.draw_text(ui_font.get(), "Press Enter", scaled(24), scaled(54), primary);
970 } else if (game_over) {
971 renderer.draw_text(ui_font.get(), std::format("Game Over: Lines cleared: {}", lines), scaled(24), scaled(22), primary);
972 renderer.draw_text(ui_font.get(), "Press Enter to Restart", scaled(24), scaled(50), primary);
973 } else {
974 renderer.draw_text(
975 ui_font.get(),
976 std::format("Level {} Lines {} Difficulty {}", level, lines, difficulty + 1),
977 scaled(24),
978 scaled(22),
979 primary);
980 }
981 draw_next_piece_preview();
982 }
983
984 void draw_next_piece_preview() {
985 if (!game_started || intro_active || game_over) {
986 return;
987 }
988
989 const int panel_size = std::min({
990 scaled(180),
991 static_cast<int>(static_cast<float>(renderer.width()) * 0.22f),
992 static_cast<int>(static_cast<float>(renderer.height()) * 0.30f),
993 });
994 if (panel_size < scaled(72)) {
995 return;
996 }
997
998 const int margin = scaled(24);
999 const int panel_x = renderer.width() - panel_size - margin;
1000 const int panel_y = scaled(88);
1001 const int border = scaled(4);
1002 const mxvk::MXCOLOR white = mxvk::MXVK_RGB(255, 255, 255);
1003 renderer.draw_rectangle(panel_x, panel_y, panel_size, border, white);
1004 renderer.draw_rectangle(panel_x, panel_y + panel_size - border, panel_size, border, white);
1005 renderer.draw_rectangle(panel_x, panel_y, border, panel_size, white);
1006 renderer.draw_rectangle(panel_x + panel_size - border, panel_y, border, panel_size, white);
1007 renderer.draw_text(ui_font.get(), "Next", panel_x + scaled(12), panel_y - scaled(28), SDL_Color{255, 255, 255, 255});
1008
1009 int min_x = next_piece.blocks[0].x;
1010 int max_x = next_piece.blocks[0].x;
1011 int min_y = next_piece.blocks[0].y;
1012 int max_y = next_piece.blocks[0].y;
1013 for (const Block &block : next_piece.blocks) {
1014 min_x = std::min(min_x, block.x);
1015 max_x = std::max(max_x, block.x);
1016 min_y = std::min(min_y, block.y);
1017 max_y = std::max(max_y, block.y);
1018 }
1019
1020 const float inner_padding = static_cast<float>(scaled(28));
1021 const float inner_size = static_cast<float>(panel_size) - inner_padding * 2.0f;
1022 const int cells_wide = max_x - min_x + 1;
1023 const int cells_high = max_y - min_y + 1;
1024 const int block_size = static_cast<int>(std::min(static_cast<float>(scaled(34)), inner_size / static_cast<float>(std::max(cells_wide, cells_high))));
1025 const float piece_width = static_cast<float>(cells_wide * block_size);
1026 const float piece_height = static_cast<float>(cells_high * block_size);
1027 const float origin_x = static_cast<float>(panel_x) + static_cast<float>(panel_size) * 0.5f - piece_width * 0.5f;
1028 const float origin_y = static_cast<float>(panel_y) + static_cast<float>(panel_size) * 0.5f - piece_height * 0.5f;
1029
1030 for (const Block &block : next_piece.blocks) {
1031 const int x = static_cast<int>(origin_x + static_cast<float>(block.x - min_x) * static_cast<float>(block_size));
1032 const int y = static_cast<int>(origin_y + static_cast<float>(block.y - min_y) * static_cast<float>(block_size));
1033 renderer.draw_block_image(block.type, x, y, block_size, block_size);
1034 }
1035 }
1036
1037 void finish_intro() {
1038 intro_active = false;
1039 game_started = true;
1040 const auto now = std::chrono::steady_clock::now();
1041 last_fall = now;
1042 last_process = now;
1043 last_input_update = now;
1044 reset_held_piece_input();
1045 reset_held_gamepad_input();
1046 }
1047
1048 void randomize_wildcard_color() {
1049 std::uniform_int_distribution<int> distribution(0, 254);
1050 wildcard_color = {
1051 static_cast<float>(distribution(rng)) / 255.0f,
1052 static_cast<float>(distribution(rng)) / 255.0f,
1053 static_cast<float>(distribution(rng)) / 255.0f,
1054 1.0f,
1055 };
1056 }
1057
1058 void draw_scene() {
1059 renderer.set_view(grid_yaw, grid_pitch, camera_distance);
1060 renderer.begin_frame(intro_active);
1061 if (intro_active) {
1062 return;
1063 }
1064
1065 const float center_x = static_cast<float>(BOARD_WIDTH - 1) * 0.5f;
1066 const float center_y = static_cast<float>(BOARD_HEIGHT - 1) * 0.5f;
1067 const auto draw_cell = [&](BlockType type, int x, int y, float z = 0.0f) {
1068 const float block_x = (static_cast<float>(x) - center_x) * BLOCK_SPACING;
1069 const float block_y = (center_y - static_cast<float>(y)) * BLOCK_SPACING;
1070 if (type == BlockType::Match || type == BlockType::Clear) {
1071 renderer.draw_wildcard(block_x, block_y, z, BLOCK_HALF_EXTENT, wildcard_color);
1072 return;
1073 }
1074 renderer.draw_block(
1075 type,
1076 block_x,
1077 block_y,
1078 z,
1079 BLOCK_HALF_EXTENT,
1080 {1.0f, 1.0f, 1.0f, 1.0f});
1081 };
1082
1083 const float frame_x = center_x * BLOCK_SPACING + BLOCK_HALF_EXTENT + FRAME_HALF_EXTENT + FRAME_GAP;
1084 const float frame_y = center_y * BLOCK_SPACING + BLOCK_HALF_EXTENT + FRAME_HALF_EXTENT + FRAME_GAP;
1085 for (int y = -1; y <= BOARD_HEIGHT; ++y) {
1086 renderer.draw_solid_cube(-frame_x, (center_y - static_cast<float>(y)) * BLOCK_SPACING, 0.04f, FRAME_HALF_EXTENT, mxvk::MXVK_RGB(110, 124, 142));
1087 renderer.draw_solid_cube(frame_x, (center_y - static_cast<float>(y)) * BLOCK_SPACING, 0.04f, FRAME_HALF_EXTENT, mxvk::MXVK_RGB(110, 124, 142));
1088 }
1089 for (int x = 0; x < BOARD_WIDTH; ++x) {
1090 renderer.draw_solid_cube((static_cast<float>(x) - center_x) * BLOCK_SPACING, -frame_y, 0.04f, FRAME_HALF_EXTENT, mxvk::MXVK_RGB(110, 124, 142));
1091 }
1092
1093 for (int y = 0; y < BOARD_HEIGHT; ++y) {
1094 for (int x = 0; x < BOARD_WIDTH; ++x) {
1095 const Cell &cell = board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)];
1096 if (cell.type == BlockType::Null || (cell.type == BlockType::Clear && ((cell.flash_counter / 6) % 2) != 0)) {
1097 continue;
1098 }
1099 draw_cell(cell.type, x, y);
1100 }
1101 }
1102 if (game_started && !game_over) {
1103 for (const Block &block : piece.blocks) {
1104 draw_cell(block.type, block.x, block.y, -0.03f);
1105 }
1106 }
1107 renderer.resolve_multisampling();
1108 }
1109
1110 void handle_view_controls(const bool *keys, float delta_seconds) {
1111 if (keys[SDL_SCANCODE_A]) {
1112 grid_yaw -= 115.0f * delta_seconds;
1113 }
1114 if (keys[SDL_SCANCODE_D]) {
1115 grid_yaw += 115.0f * delta_seconds;
1116 }
1117 if (keys[SDL_SCANCODE_W]) {
1118 grid_pitch = std::clamp(grid_pitch + 90.0f * delta_seconds, -70.0f, 70.0f);
1119 }
1120 if (keys[SDL_SCANCODE_S]) {
1121 grid_pitch = std::clamp(grid_pitch - 90.0f * delta_seconds, -70.0f, 70.0f);
1122 }
1123 if (keys[SDL_SCANCODE_PAGEUP]) {
1124 camera_distance = std::max(2.7f, camera_distance - 2.0f * delta_seconds);
1125 }
1126 if (keys[SDL_SCANCODE_PAGEDOWN]) {
1127 camera_distance = std::min(7.0f, camera_distance + 2.0f * delta_seconds);
1128 }
1129 }
1130
1131 void handle_piece_controls(const bool *keys, float delta_seconds) {
1132 if (!game_started || game_over) {
1133 reset_held_piece_input();
1134 return;
1135 }
1136
1137 const bool left = keys[SDL_SCANCODE_LEFT];
1138 const bool right = keys[SDL_SCANCODE_RIGHT];
1139 const int direction = (left == right) ? 0 : (left ? -1 : 1);
1140 if (direction == 0) {
1141 horizontal_move_direction = 0;
1142 horizontal_move_timer = 0.0f;
1143 } else {
1144 constexpr float INITIAL_DELAY_SECONDS = 0.16f;
1145 constexpr float REPEAT_SECONDS = 0.065f;
1146 if (horizontal_move_direction != direction) {
1147 horizontal_move_direction = direction;
1148 horizontal_move_timer = -INITIAL_DELAY_SECONDS;
1149 move_piece_horizontal(direction);
1150 } else {
1151 horizontal_move_timer += delta_seconds;
1152 while (horizontal_move_timer >= 0.0f) {
1153 horizontal_move_timer -= REPEAT_SECONDS;
1154 move_piece_horizontal(direction);
1155 }
1156 }
1157 }
1158
1159 if (keys[SDL_SCANCODE_DOWN]) {
1160 constexpr float SOFT_DROP_REPEAT_SECONDS = 0.045f;
1161 if (!soft_drop_held) {
1162 soft_drop_held = true;
1163 soft_drop_timer = 0.0f;
1164 key_down();
1165 last_fall = std::chrono::steady_clock::now();
1166 } else {
1167 soft_drop_timer += delta_seconds;
1168 while (soft_drop_timer >= SOFT_DROP_REPEAT_SECONDS) {
1169 soft_drop_timer -= SOFT_DROP_REPEAT_SECONDS;
1170 key_down();
1171 last_fall = std::chrono::steady_clock::now();
1172 }
1173 }
1174 } else {
1175 soft_drop_held = false;
1176 soft_drop_timer = 0.0f;
1177 }
1178
1179 if (keys[SDL_SCANCODE_UP]) {
1180 constexpr float CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
1181 constexpr float CYCLE_REPEAT_SECONDS = 0.11f;
1182 if (!cycle_held) {
1183 cycle_held = true;
1184 cycle_timer = -CYCLE_INITIAL_DELAY_SECONDS;
1185 cycle_piece_blocks();
1186 } else {
1187 cycle_timer += delta_seconds;
1188 while (cycle_timer >= 0.0f) {
1189 cycle_timer -= CYCLE_REPEAT_SECONDS;
1190 cycle_piece_blocks();
1191 }
1192 }
1193 } else {
1194 cycle_held = false;
1195 cycle_timer = 0.0f;
1196 }
1197 }
1198
1199 void handle_gamepad_input(float delta_seconds) {
1200 if (gamepad == nullptr || !game_started || game_over) {
1201 reset_held_gamepad_input();
1202 return;
1203 }
1204
1205 const Sint16 left_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
1206 const Sint16 left_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1207 const Sint16 right_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX);
1208 const Sint16 right_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
1209
1210 const bool dpad_left = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
1211 const bool dpad_right = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
1212 const bool dpad_down = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
1213 const bool dpad_up = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP);
1214
1215 const int move_direction = dpad_left == dpad_right
1216 ? ((left_x < -GAMEPAD_DEADZONE) ? -1 : (left_x > GAMEPAD_DEADZONE) ? 1
1217 : 0)
1218 : (dpad_left ? -1 : 1);
1219 if (move_direction == 0) {
1220 gamepad_move_direction = 0;
1221 gamepad_move_held_seconds = 0.0f;
1222 gamepad_move_repeat_timer = 0.0f;
1223 } else if (move_direction != gamepad_move_direction) {
1224 gamepad_move_direction = move_direction;
1225 gamepad_move_held_seconds = 0.0f;
1226 gamepad_move_repeat_timer = 0.0f;
1227 move_piece_horizontal(gamepad_move_direction);
1228 } else {
1229 gamepad_move_held_seconds += delta_seconds;
1230 const float threshold = (gamepad_move_held_seconds < GAMEPAD_MOVE_INITIAL_DELAY_SECONDS)
1231 ? GAMEPAD_MOVE_INITIAL_DELAY_SECONDS
1232 : GAMEPAD_MOVE_REPEAT_SECONDS;
1233 gamepad_move_repeat_timer += delta_seconds;
1234 if (gamepad_move_repeat_timer >= threshold) {
1235 move_piece_horizontal(gamepad_move_direction);
1236 gamepad_move_repeat_timer = 0.0f;
1237 }
1238 }
1239
1240 const bool soft_drop_down = dpad_down || left_y > GAMEPAD_DEADZONE;
1241 if (!soft_drop_down) {
1242 gamepad_soft_drop_held = false;
1243 gamepad_soft_drop_repeat_timer = 0.0f;
1244 } else {
1245 const float threshold = gamepad_soft_drop_held ? GAMEPAD_SOFT_DROP_REPEAT_SECONDS : GAMEPAD_SOFT_DROP_INITIAL_DELAY_SECONDS;
1246 gamepad_soft_drop_repeat_timer += delta_seconds;
1247 if (gamepad_soft_drop_repeat_timer >= threshold) {
1248 key_down();
1249 last_fall = std::chrono::steady_clock::now();
1250 gamepad_soft_drop_repeat_timer = 0.0f;
1251 gamepad_soft_drop_held = true;
1252 }
1253 }
1254
1255 if (!dpad_up) {
1256 gamepad_cycle_held = false;
1257 gamepad_cycle_repeat_timer = 0.0f;
1258 } else {
1259 const float threshold = gamepad_cycle_held ? GAMEPAD_CYCLE_REPEAT_SECONDS : GAMEPAD_CYCLE_INITIAL_DELAY_SECONDS;
1260 gamepad_cycle_repeat_timer += delta_seconds;
1261 if (gamepad_cycle_repeat_timer >= threshold) {
1262 cycle_piece_blocks();
1263 gamepad_cycle_repeat_timer = 0.0f;
1264 gamepad_cycle_held = true;
1265 }
1266 }
1267
1268 if (std::abs(right_x) > GAMEPAD_DEADZONE) {
1269 grid_yaw += static_cast<float>(right_x) * GAMEPAD_STICK_SCALE * GAMEPAD_STICK_ROTATE_SPEED * delta_seconds;
1270 }
1271 if (std::abs(right_y) > GAMEPAD_DEADZONE) {
1272 grid_pitch = std::clamp(
1273 grid_pitch - static_cast<float>(right_y) * GAMEPAD_STICK_SCALE * GAMEPAD_STICK_PITCH_SPEED * delta_seconds,
1274 -70.0f,
1275 70.0f);
1276 }
1277
1278 constexpr float ZOOM_SPEED = 2.0f;
1279 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
1280 camera_distance = std::min(7.0f, camera_distance + ZOOM_SPEED * delta_seconds);
1281 }
1282 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
1283 camera_distance = std::max(2.7f, camera_distance - ZOOM_SPEED * delta_seconds);
1284 }
1285 }
1286
1287 void handle_gamepad_button_down(Uint8 button) {
1288 if (intro_active) {
1289 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
1290 finish_intro();
1291 }
1292 return;
1293 }
1294
1295 if (game_over) {
1296 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
1297 reset_game();
1298 game_started = true;
1299 } else if (button == SDL_GAMEPAD_BUTTON_BACK) {
1300 exit();
1301 }
1302 return;
1303 }
1304
1305 if (!game_started) {
1306 return;
1307 }
1308
1309 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
1310 rotate_right();
1311 } else if (button == SDL_GAMEPAD_BUTTON_WEST) {
1312 rotate_left();
1313 } else if (button == SDL_GAMEPAD_BUTTON_EAST) {
1314 hard_drop();
1315 } else if (button == SDL_GAMEPAD_BUTTON_BACK) {
1316 exit();
1317 }
1318 }
1319
1320 void reset_held_piece_input() {
1321 horizontal_move_timer = 0.0f;
1322 soft_drop_timer = 0.0f;
1323 cycle_timer = 0.0f;
1324 horizontal_move_direction = 0;
1325 soft_drop_held = false;
1326 cycle_held = false;
1327 }
1328
1329 void reset_held_gamepad_input() {
1330 gamepad_move_repeat_timer = 0.0f;
1331 gamepad_soft_drop_repeat_timer = 0.0f;
1332 gamepad_cycle_repeat_timer = 0.0f;
1333 gamepad_move_held_seconds = 0.0f;
1334 gamepad_move_direction = 0;
1335 gamepad_soft_drop_held = false;
1336 gamepad_cycle_held = false;
1337 }
1338
1339 void move_piece_horizontal(int direction) {
1340 if (!check_piece(piece, direction, 0)) {
1341 return;
1342 }
1343 if (direction < 0) {
1344 piece.move_left();
1345 } else {
1346 piece.move_right();
1347 }
1348 }
1349
1350 void cycle_piece_blocks() {
1351 piece.shift(ShiftDirection::Up);
1352 }
1353
1354 void hard_drop() {
1355 if (!game_started || game_over) {
1356 return;
1357 }
1358 while (check_piece(piece, 0, 1)) {
1359 piece.move_down();
1360 }
1361 key_down();
1362 last_fall = std::chrono::steady_clock::now();
1363 }
1364
1365 bool open_gamepad(SDL_JoystickID id) {
1366 if (gamepad != nullptr && gamepad_id == id) {
1367 return true;
1368 }
1369 close_gamepad();
1370 gamepad = SDL_OpenGamepad(id);
1371 if (gamepad == nullptr) {
1372 return false;
1373 }
1374 gamepad_id = id;
1375 return true;
1376 }
1377
1378 void close_gamepad() {
1379 if (gamepad != nullptr) {
1380 SDL_CloseGamepad(gamepad);
1381 gamepad = nullptr;
1382 gamepad_id = 0;
1383 }
1384 }
1385
1386 void try_open_first_gamepad() {
1387 if (gamepad != nullptr) {
1388 return;
1389 }
1390 int count = 0;
1391 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1392 if (ids == nullptr || count <= 0) {
1393 if (ids != nullptr) {
1394 SDL_free(ids);
1395 }
1396 return;
1397 }
1398 open_gamepad(ids[0]);
1399 SDL_free(ids);
1400 }
1401
1402 void reset_game() {
1403 reset_held_piece_input();
1404 reset_held_gamepad_input();
1405 for (auto &row : board) {
1406 for (Cell &cell : row) {
1407 cell = {};
1408 }
1409 }
1410 level = 1;
1411 lines = 0;
1412 game_over = false;
1413 piece.new_piece(BOARD_WIDTH / 2, 0, rng);
1414 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
1415 last_fall = std::chrono::steady_clock::now();
1416 last_process = last_fall;
1417 }
1418
1419 void key_down() {
1420 if (check_piece(piece, 0, 1)) {
1421 piece.move_down();
1422 return;
1423 }
1424 set_piece();
1425 piece = next_piece;
1426 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
1427 if (!check_piece(piece, 0, 0)) {
1428 game_over = true;
1429 }
1430 }
1431
1432 [[nodiscard]] bool check_piece(const Piece &test_piece, int offset_x, int offset_y) const {
1433 for (const Block &block : test_piece.blocks) {
1434 const int x = block.x + offset_x;
1435 const int y = block.y + offset_y;
1436 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
1437 return false;
1438 }
1439 const BlockType type = board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)].type;
1440 if (type != BlockType::Null && type != BlockType::Clear) {
1441 return false;
1442 }
1443 }
1444 return true;
1445 }
1446
1447 void set_piece() {
1448 for (const Block &block : piece.blocks) {
1449 if (block.x < 0 || block.x >= BOARD_WIDTH || block.y < 0 || block.y >= BOARD_HEIGHT) {
1450 continue;
1451 }
1452 Cell &cell = board[static_cast<std::size_t>(block.y)][static_cast<std::size_t>(block.x)];
1453 cell.type = block.type;
1454 cell.clear_value = 0;
1455 cell.flash_counter = 0;
1456 if (block.y == 0) {
1457 game_over = true;
1458 }
1459 }
1460 }
1461
1462 void rotate_left() {
1463 if (!game_started || game_over) {
1464 return;
1465 }
1466 Piece test_piece = piece;
1467 test_piece.rotate_left();
1468 if (check_piece(test_piece, 0, 0)) {
1469 piece = test_piece;
1470 }
1471 }
1472
1473 void rotate_right() {
1474 if (!game_started || game_over) {
1475 return;
1476 }
1477 Piece test_piece = piece;
1478 test_piece.rotate_right();
1479 if (check_piece(test_piece, 0, 0)) {
1480 piece = test_piece;
1481 }
1482 }
1483
1484 bool proc_blocks() {
1485 constexpr std::array<std::array<int, 2>, 4> DIRECTIONS{{
1486 {{1, 0}},
1487 {{0, 1}},
1488 {{1, 1}},
1489 {{1, -1}},
1490 }};
1491 constexpr std::array<BlockType, 3> COLOR_STARTS{BlockType::Red1, BlockType::Green1, BlockType::Blue1};
1492 for (int y = 0; y < BOARD_HEIGHT; ++y) {
1493 for (int x = 0; x < BOARD_WIDTH; ++x) {
1494 for (const auto &direction : DIRECTIONS) {
1495 for (BlockType start : COLOR_STARTS) {
1496 const BlockType one = start;
1497 const BlockType two = static_cast<BlockType>(static_cast<int>(start) + 1);
1498 const BlockType three = static_cast<BlockType>(static_cast<int>(start) + 2);
1499 if (check_sequence(x, y, direction[0], direction[1], one, two, three) ||
1500 check_sequence(x, y, direction[0], direction[1], three, two, one)) {
1501 mark_clear(x, y, direction[0], direction[1]);
1502 add_score();
1503 return true;
1504 }
1505 }
1506 }
1507 }
1508 }
1509 return false;
1510 }
1511
1512 bool proc_move_down() {
1513 for (int y = BOARD_HEIGHT - 2; y >= 0; --y) {
1514 for (int x = 0; x < BOARD_WIDTH; ++x) {
1515 Cell &source = board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)];
1516 Cell &target = board[static_cast<std::size_t>(y + 1)][static_cast<std::size_t>(x)];
1517 if (is_play_block(source.type) && target.type == BlockType::Null) {
1518 target = source;
1519 source = {};
1520 return true;
1521 }
1522 }
1523 }
1524 bool updated = false;
1525 for (auto &row : board) {
1526 for (Cell &cell : row) {
1527 if (cell.type == BlockType::Clear) {
1528 ++cell.clear_value;
1529 ++cell.flash_counter;
1530 if (cell.clear_value > 50) {
1531 cell = {};
1532 }
1533 updated = true;
1534 }
1535 }
1536 }
1537 return updated;
1538 }
1539
1540 [[nodiscard]] bool check_sequence(int x, int y, int dx, int dy, BlockType first, BlockType second, BlockType third) const {
1541 return check_block(x, y, first) && check_block(x + dx, y + dy, second) && check_block(x + dx * 2, y + dy * 2, third);
1542 }
1543
1544 [[nodiscard]] bool check_block(int x, int y, BlockType expected) const {
1545 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
1546 return false;
1547 }
1548 return same_or_match(board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)].type, expected);
1549 }
1550
1551 void mark_clear(int x, int y, int dx, int dy) {
1552 for (int index = 0; index < 3; ++index) {
1553 Cell &cell = board[static_cast<std::size_t>(y + dy * index)][static_cast<std::size_t>(x + dx * index)];
1554 cell.type = BlockType::Clear;
1555 cell.clear_value = 1;
1556 cell.flash_counter = 0;
1557 }
1558 }
1559
1560 void add_score() {
1561 ++lines;
1562 if ((lines % 6) == 0 && level < LEVEL_COUNT) {
1563 ++level;
1564 }
1565 }
1566 };
1567} // namespace
1568
1569int main(int argc, char **argv) {
1570 try {
1571 const Arguments args = proc_args(argc, argv);
1572 const FramebufferDimensions framebuffer = args.framebufferSpecified
1573 ? args.framebuffer
1575 PuzzleDropWindow window(args, framebuffer);
1576 window.loop();
1577 } catch (const mxvk::Exception &exception) {
1578 std::cerr << std::format("mxvk: Exception: {}\n", exception.text());
1579 return EXIT_FAILURE;
1580 } catch (const ArgException<std::string> &exception) {
1581 std::cerr << std::format("mxvk: Argument Exception: {}\n", exception.text());
1582 return EXIT_FAILURE;
1583 } catch (const std::exception &exception) {
1584 std::cerr << std::format("3dmath_puzzle_drop: Exception: {}\n", exception.what());
1585 return EXIT_FAILURE;
1586 }
1587 return EXIT_SUCCESS;
1588}
constexpr int BLOCK_SPACING
Definition acid.drop.cpp:29
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Definition argz.hpp:872
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:178
void proc() override
Execute one processing/update step.
Definition main.cpp:858
void event(SDL_Event &event) override
Handle one SDL event.
Definition main.cpp:796
PuzzleDropWindow(const Arguments &args, const FramebufferDimensions &framebuffer)
Definition main.cpp:778
void draw_block(BlockType type, float x, float y, float z, float half_extent, const mxvk::vec4D &tint)
Definition main.cpp:419
void draw_text(TTF_Font *font, const std::string &text, int x, int y, const SDL_Color &color)
Definition main.cpp:479
void draw_wildcard(float x, float y, float z, float half_extent, const mxvk::vec4D &color)
Definition main.cpp:423
void draw_rectangle(int left, int top, int width, int height, mxvk::MXCOLOR color)
Definition main.cpp:445
void draw_solid_cube(float x, float y, float z, float half_extent, mxvk::MXCOLOR color)
Definition main.cpp:436
void set_view(float yaw, float pitch, float distance)
Definition main.cpp:374
void draw_block_image(BlockType type, int left, int top, int width, int height)
Definition main.cpp:458
SoftwareRenderer(int width, int height, const std::string &data_root, bool enable_warp_fix, bool enable_mipmapping, float mip_bias)
Definition main.cpp:343
void operator()(SDL_Surface *surface) const
Definition main.cpp:58
std::string text() const
Small RAII wrapper for an SDL_ttf font handle.
Definition mxvk_text.hpp:46
Four-by-four homogeneous transform matrix.
Definition mxvk_math.h:812
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
Definition mxvk.cpp:3477
VkExtent2D swapchain_extent
Definition mxvk.hpp:495
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:593
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VK_Window()=default
Construct an empty window object.
Two-dimensional float vector with common arithmetic helpers.
Definition mxvk_math.h:177
Four-dimensional float vector used for homogeneous 3D coordinates.
Definition mxvk_math.h:416
float y
Y coordinate.
Definition mxvk_math.h:422
float x
X coordinate.
Definition mxvk_math.h:419
void Normalize()
Normalize the 3D components in place and reset W to 1.
Definition mxvk_math.h:518
float z
Z coordinate.
Definition mxvk_math.h:425
#define math3d_puzzle_drop_ASSET_DIR
Definition main.cpp:28
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
PNG image loading and saving utilities via SDL3.
constexpr int piece_height
Definition main.cpp:32
constexpr int LEVEL_COUNT
Definition main.cpp:36
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
Definition main.cpp:29
constexpr float FRAME_GAP
Definition main.cpp:40
int texture_index(BlockType type)
Definition main.cpp:182
constexpr float CAMERA_DISTANCE
Definition main.cpp:36
constexpr int DEFAULT_FRAME_WIDTH
Definition main.cpp:26
constexpr float FRAME_HALF_EXTENT
Definition main.cpp:39
Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps)
Definition main.cpp:160
constexpr float BLOCK_SPACING
Definition main.cpp:37
constexpr std::array< const char *, 10 > BLOCK_TEXTURE_FILES
Definition main.cpp:43
constexpr std::array< float, 3 > FALL_SECONDS
Definition main.cpp:42
constexpr int BOARD_WIDTH
Definition main.cpp:32
constexpr int DEFAULT_FRAME_HEIGHT
Definition main.cpp:27
constexpr float BLOCK_HALF_EXTENT
Definition main.cpp:38
void build_mipmaps(Texture &texture)
Definition main.cpp:256
bool same_or_match(BlockType actual, BlockType expected)
Definition main.cpp:186
constexpr int BOARD_HEIGHT
Definition main.cpp:33
bool is_play_block(BlockType type)
Definition main.cpp:178
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
Definition mxvk_math.h:54
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
Definition mxvk_math.h:40
void BuildTables()
Rebuild the sine and cosine lookup tables.
Definition mxvk_math.h:113
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:103
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
Definition mxvk_math.h:59
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
Definition mxvk_math.h:49
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
Definition mxvk_math.h:69
float edge_function(const vec2D &a, const vec2D &b, const vec2D &p)
Compute the signed edge function value for point p relative to edge a-b.
Definition mxvk_math.h:2127
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
Definition mxvk_math.h:37
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
Definition mxvk_math.h:64
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:730
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
Definition argz.hpp:758
bool framebufferSpecified
Whether --framebuffer was provided.
Definition argz.hpp:759
Parsed software framebuffer dimensions.
Definition argz.hpp:721
int width
Software framebuffer width in pixels.
Definition argz.hpp:722
int height
Software framebuffer height in pixels.
Definition argz.hpp:723
void new_piece(int start_x, int start_y, std::mt19937 &rng)
Definition main.cpp:95
std::array< Block, 3 > blocks
Definition main.cpp:92
void shift(ShiftDirection direction)
Definition main.cpp:102
std::vector< mxvk::MXCOLOR > pixels
Definition main.cpp:193
std::vector< TextureLevel > mipmaps
Definition main.cpp:200
mxvk::MXCOLOR sample_filtered(float u, float v, float lod) const
Definition main.cpp:202
std::vector< mxvk::MXCOLOR > pixels
Definition main.cpp:199