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