4#if defined(MXVK_USE_EIGEN_MATH)
27#ifndef math3d_puzzle_drop_ASSET_DIR
28#define math3d_puzzle_drop_ASSET_DIR "."
56 class SurfaceDeleter {
63 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
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)};
105 blocks[0].type = types[2];
106 blocks[1].type = types[0];
107 blocks[2].type = types[1];
109 blocks[0].type = types[1];
110 blocks[1].type = types[2];
111 blocks[2].type = types[0];
166 [[nodiscard]]
static BlockType random_type(std::mt19937 &rng) {
168 return static_cast<BlockType>(distribution(rng));
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);
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);
226 level_pixels[
static_cast<std::size_t
>(y0 * level_width + x0)],
227 level_pixels[
static_cast<std::size_t
>(y0 * level_width + x1)],
230 level_pixels[
static_cast<std::size_t
>(y1 * level_width + x0)],
231 level_pixels[
static_cast<std::size_t
>(y1 * level_width + x1)],
233 return blend_color(top, bottom, y_blend);
237 const auto blend_channel = [amount](std::uint8_t left, std::uint8_t right) {
238 return static_cast<std::uint8_t
>(
240 static_cast<float>(left) + (
static_cast<float>(right) -
static_cast<float>(left)) * amount,
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) {
262 level.
width = std::max(1, source_width / 2);
263 level.
height = std::max(1, source_height / 2);
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)];
282 level.
pixels[
static_cast<std::size_t
>(y * level.
width + x)] =
283 ((alpha / 4U) << 24U) |
284 ((red / 4U) << 16U) |
285 ((green / 4U) << 8U) |
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;
299 throw mxvk::Exception(std::format(
"3dmath_puzzle_drop: failed to load PNG '{}'", path));
301 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
303 throw mxvk::Exception(std::format(
"3dmath_puzzle_drop: failed to convert PNG '{}': {}", path, SDL_GetError()));
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));
311 texture.
width = rgba->w;
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)] =
330 if (generate_mipmaps) {
344 : frame_width(
width),
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),
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()));
358 block_textures.push_back(
load_texture(data_root +
"/" + filename, mipmapping_enabled));
363 return frame_surface.get();
374 void set_view(
float yaw,
float pitch,
float distance) {
375 camera_rotation.BuildXYZ(pitch, yaw, 0.0f);
376 camera_distance = distance;
380 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
381 draw_flat_image(show_intro ? intro : background);
383 fill_translucent_rectangle(0, 0, frame_width, frame_height,
mxvk::MXVK_RGB(3, 8, 16), 150);
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);
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]
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);
420 draw_cube(&block_textures[
static_cast<std::size_t
>(
texture_index(type))], x, y, z, half_extent, tint);
425 std::max(color.
x, 0.08f),
426 std::max(color.
y, 0.08f),
427 std::max(color.
z, 0.08f),
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);
442 draw_cube(
nullptr, x, y, z, half_extent, tint);
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);
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);
474 blend_pixel(row +
static_cast<std::size_t
>(x * 4), color);
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()) {
484 SurfacePtr text_surface(TTF_RenderText_Blended(font, text.c_str(), 0, color));
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);
496 int frame_height = 0;
497 std::vector<float> depth_buffer;
498 std::vector<mxvk::MXCOLOR> color_buffer;
501 std::vector<Texture> block_textures;
502 bool warp_fix_enabled =
true;
503 bool mipmapping_enabled =
true;
504 float mip_level_bias = 0.0f;
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{{
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},
527 static constexpr std::array<std::array<int, 4>, 6> CUBE_FACES{{
536 static const std::array<std::array<mxvk::vec2D, 4>, 6> CUBE_FACE_UVS;
538 static void write_pixel(std::uint8_t *pixel,
mxvk::MXCOLOR color) {
545 static void blend_pixel(std::uint8_t *pixel,
mxvk::MXCOLOR 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);
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);
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);
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);
583 static_cast<float>(frame_width) * 0.43f + point.
x / z * scale,
584 static_cast<float>(frame_height) * 0.50f - point.
y / z * scale,
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) {
595 CUBE_VERTICES[index].x * half_extent + x,
596 CUBE_VERTICES[index].y * half_extent + y,
597 CUBE_VERTICES[index].z * half_extent + z,
599 point = camera_rotation.MulVec(point);
600 point.z += camera_distance;
601 camera_vertices[index] = point;
602 projected[index] = project(point);
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));
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) {
618 mxvk::vec4D normalized_light = light_direction;
620 float intensity = std::clamp(0.40f + std::max(0.0f, normal.DotProduct(normalized_light)) * 0.60f, 0.0f, 1.0f);
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;
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);
645 void rasterize_triangle(
const RasterVertex &a,
646 const RasterVertex &b,
647 const RasterVertex &c,
648 const Texture *texture,
649 const mxvk::vec4D &tint,
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);
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;
676 const float minification = std::max({
677 texels_per_pixel(a, b),
678 texels_per_pixel(b, c),
679 texels_per_pixel(c, a),
682 texture_lod = std::max(0.0f, std::log2(minification) + mip_level_bias);
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]);
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))) {
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]) {
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;
720 if (passing_samples == 0) {
724 const mxvk::vec2D shading_point(
725 centroid_x /
static_cast<float>(passing_sample_count),
726 centroid_y /
static_cast<float>(passing_sample_count));
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;
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;
740 texture_weight0 * a.uv.x +
741 texture_weight1 * b.uv.x +
742 texture_weight2 * c.uv.x;
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);
754 for (std::size_t sample = 0; sample < MSAA_SAMPLE_COUNT; ++sample) {
755 if ((passing_samples &
static_cast<std::uint8_t
>(1U << sample)) == 0) {
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;
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}}},
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))))) {
785 std::random_device random_device;
786 rng.seed(random_device());
787 try_open_first_gamepad();
789 intro_start = std::chrono::steady_clock::now();
797 if (
event.type == SDL_EVENT_QUIT) {
802 if (
event.type == SDL_EVENT_GAMEPAD_ADDED) {
803 if (!open_gamepad(
event.gdevice.which)) {
804 try_open_first_gamepad();
809 if (
event.type == SDL_EVENT_GAMEPAD_REMOVED) {
810 if (gamepad !=
nullptr &&
event.gdevice.which == gamepad_id) {
812 try_open_first_gamepad();
817 if (
event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
818 handle_gamepad_button_down(
event.gbutton.button);
822 if (
event.type != SDL_EVENT_KEY_DOWN ||
event.key.repeat) {
825 if (intro_active && (
event.key.key == SDLK_SPACE ||
event.key.key == SDLK_RETURN ||
event.key.key == SDLK_KP_ENTER)) {
829 switch (
event.key.key) {
843 difficulty =
static_cast<int>(
event.key.key - SDLK_1);
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) {
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);
874 handle_gamepad_input(delta_seconds);
877 if (game_started && !game_over) {
878 if (std::chrono::duration<float>(now - last_fall).count() >=
FALL_SECONDS[
static_cast<std::size_t
>(difficulty)]) {
882 if (std::chrono::duration<float>(now - last_process).count() >= 0.018f) {
891 ensure_frame_sprite();
892 frame_sprite->updateTexture(renderer.surface());
895 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
899 std::string data_root;
904 std::array<std::array<Cell, BOARD_WIDTH>, BOARD_HEIGHT> board{};
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;
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;
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;
947 [[nodiscard]]
static float framebuffer_scale(
const FramebufferDimensions &framebuffer) {
949 static_cast<float>(framebuffer.
width) /
static_cast<float>(DEFAULT_FRAME_WIDTH),
950 static_cast<float>(framebuffer.
height) /
static_cast<float>(DEFAULT_FRAME_HEIGHT));
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()}))));
957 void ensure_frame_sprite() {
958 if (frame_sprite !=
nullptr) {
963 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
966 void draw_interface() {
967 const SDL_Color primary{255, 244, 223, 255};
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);
976 std::format(
"Level {} Lines {} Difficulty {}", level, lines, difficulty + 1),
981 draw_next_piece_preview();
984 void draw_next_piece_preview() {
985 if (!game_started || intro_active || game_over) {
989 const int panel_size = std::min({
991 static_cast<int>(
static_cast<float>(renderer.width()) * 0.22f),
992 static_cast<int>(
static_cast<float>(renderer.height()) * 0.30f),
994 if (panel_size < scaled(72)) {
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);
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});
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);
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;
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);
1037 void finish_intro() {
1038 intro_active =
false;
1039 game_started =
true;
1040 const auto now = std::chrono::steady_clock::now();
1043 last_input_update = now;
1044 reset_held_piece_input();
1045 reset_held_gamepad_input();
1048 void randomize_wildcard_color() {
1049 std::uniform_int_distribution<int> distribution(0, 254);
1051 static_cast<float>(distribution(rng)) / 255.0f,
1052 static_cast<float>(distribution(rng)) / 255.0f,
1053 static_cast<float>(distribution(rng)) / 255.0f,
1059 renderer.set_view(grid_yaw, grid_pitch, camera_distance);
1060 renderer.begin_frame(intro_active);
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;
1071 renderer.draw_wildcard(block_x, block_y, z, BLOCK_HALF_EXTENT, wildcard_color);
1074 renderer.draw_block(
1080 {1.0f, 1.0f, 1.0f, 1.0f});
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));
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));
1095 const Cell &cell = board[
static_cast<std::size_t
>(y)][
static_cast<std::size_t
>(x)];
1099 draw_cell(cell.type, x, y);
1102 if (game_started && !game_over) {
1103 for (
const Block &block : piece.blocks) {
1104 draw_cell(block.type, block.x, block.y, -0.03f);
1107 renderer.resolve_multisampling();
1110 void handle_view_controls(
const bool *keys,
float delta_seconds) {
1111 if (keys[SDL_SCANCODE_A]) {
1112 grid_yaw -= 115.0f * delta_seconds;
1114 if (keys[SDL_SCANCODE_D]) {
1115 grid_yaw += 115.0f * delta_seconds;
1117 if (keys[SDL_SCANCODE_W]) {
1118 grid_pitch = std::clamp(grid_pitch + 90.0f * delta_seconds, -70.0f, 70.0f);
1120 if (keys[SDL_SCANCODE_S]) {
1121 grid_pitch = std::clamp(grid_pitch - 90.0f * delta_seconds, -70.0f, 70.0f);
1123 if (keys[SDL_SCANCODE_PAGEUP]) {
1124 camera_distance = std::max(2.7f, camera_distance - 2.0f * delta_seconds);
1126 if (keys[SDL_SCANCODE_PAGEDOWN]) {
1127 camera_distance = std::min(7.0f, camera_distance + 2.0f * delta_seconds);
1131 void handle_piece_controls(
const bool *keys,
float delta_seconds) {
1132 if (!game_started || game_over) {
1133 reset_held_piece_input();
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;
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);
1151 horizontal_move_timer += delta_seconds;
1152 while (horizontal_move_timer >= 0.0f) {
1153 horizontal_move_timer -= REPEAT_SECONDS;
1154 move_piece_horizontal(direction);
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;
1165 last_fall = std::chrono::steady_clock::now();
1167 soft_drop_timer += delta_seconds;
1168 while (soft_drop_timer >= SOFT_DROP_REPEAT_SECONDS) {
1169 soft_drop_timer -= SOFT_DROP_REPEAT_SECONDS;
1171 last_fall = std::chrono::steady_clock::now();
1175 soft_drop_held =
false;
1176 soft_drop_timer = 0.0f;
1179 if (keys[SDL_SCANCODE_UP]) {
1180 constexpr float CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
1181 constexpr float CYCLE_REPEAT_SECONDS = 0.11f;
1184 cycle_timer = -CYCLE_INITIAL_DELAY_SECONDS;
1185 cycle_piece_blocks();
1187 cycle_timer += delta_seconds;
1188 while (cycle_timer >= 0.0f) {
1189 cycle_timer -= CYCLE_REPEAT_SECONDS;
1190 cycle_piece_blocks();
1199 void handle_gamepad_input(
float delta_seconds) {
1200 if (gamepad ==
nullptr || !game_started || game_over) {
1201 reset_held_gamepad_input();
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);
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);
1215 const int move_direction = dpad_left == dpad_right
1216 ? ((left_x < -GAMEPAD_DEADZONE) ? -1 : (left_x > GAMEPAD_DEADZONE) ? 1
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);
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;
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;
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) {
1249 last_fall = std::chrono::steady_clock::now();
1250 gamepad_soft_drop_repeat_timer = 0.0f;
1251 gamepad_soft_drop_held =
true;
1256 gamepad_cycle_held =
false;
1257 gamepad_cycle_repeat_timer = 0.0f;
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;
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;
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,
1279 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
1280 camera_distance = std::min(7.0f, camera_distance + ZOOM_SPEED * delta_seconds);
1282 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
1283 camera_distance = std::max(2.7f, camera_distance - ZOOM_SPEED * delta_seconds);
1287 void handle_gamepad_button_down(Uint8 button) {
1289 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
1296 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
1298 game_started =
true;
1299 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
1305 if (!game_started) {
1309 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
1311 }
else if (button == SDL_GAMEPAD_BUTTON_WEST) {
1313 }
else if (button == SDL_GAMEPAD_BUTTON_EAST) {
1315 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
1320 void reset_held_piece_input() {
1321 horizontal_move_timer = 0.0f;
1322 soft_drop_timer = 0.0f;
1324 horizontal_move_direction = 0;
1325 soft_drop_held =
false;
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;
1339 void move_piece_horizontal(
int direction) {
1340 if (!check_piece(piece, direction, 0)) {
1343 if (direction < 0) {
1350 void cycle_piece_blocks() {
1355 if (!game_started || game_over) {
1358 while (check_piece(piece, 0, 1)) {
1362 last_fall = std::chrono::steady_clock::now();
1365 bool open_gamepad(SDL_JoystickID
id) {
1366 if (gamepad !=
nullptr && gamepad_id ==
id) {
1370 gamepad = SDL_OpenGamepad(
id);
1371 if (gamepad ==
nullptr) {
1378 void close_gamepad() {
1379 if (gamepad !=
nullptr) {
1380 SDL_CloseGamepad(gamepad);
1386 void try_open_first_gamepad() {
1387 if (gamepad !=
nullptr) {
1391 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1392 if (ids ==
nullptr || count <= 0) {
1393 if (ids !=
nullptr) {
1398 open_gamepad(ids[0]);
1403 reset_held_piece_input();
1404 reset_held_gamepad_input();
1405 for (
auto &row : board) {
1406 for (Cell &cell : row) {
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;
1420 if (check_piece(piece, 0, 1)) {
1426 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
1427 if (!check_piece(piece, 0, 0)) {
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) {
1439 const BlockType type = board[
static_cast<std::size_t
>(y)][
static_cast<std::size_t
>(x)].type;
1448 for (
const Block &block : piece.blocks) {
1449 if (block.x < 0 || block.x >= BOARD_WIDTH || block.y < 0 || block.y >= BOARD_HEIGHT) {
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;
1462 void rotate_left() {
1463 if (!game_started || game_over) {
1466 Piece test_piece = piece;
1467 test_piece.rotate_left();
1468 if (check_piece(test_piece, 0, 0)) {
1473 void rotate_right() {
1474 if (!game_started || game_over) {
1477 Piece test_piece = piece;
1478 test_piece.rotate_right();
1479 if (check_piece(test_piece, 0, 0)) {
1484 bool proc_blocks() {
1485 constexpr std::array<std::array<int, 2>, 4> DIRECTIONS{{
1494 for (
const auto &direction : DIRECTIONS) {
1495 for (BlockType start : COLOR_STARTS) {
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]);
1512 bool proc_move_down() {
1513 for (
int y = BOARD_HEIGHT - 2; y >= 0; --y) {
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)];
1524 bool updated =
false;
1525 for (
auto &row : board) {
1526 for (Cell &cell : row) {
1529 ++cell.flash_counter;
1530 if (cell.clear_value > 50) {
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);
1544 [[nodiscard]]
bool check_block(
int x,
int y, BlockType expected)
const {
1545 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
1548 return same_or_match(board[
static_cast<std::size_t
>(y)][
static_cast<std::size_t
>(x)].type, expected);
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)];
1555 cell.clear_value = 1;
1556 cell.flash_counter = 0;
1562 if ((lines % 6) == 0 && level < LEVEL_COUNT) {
1578 std::cerr << std::format(
"mxvk: Exception: {}\n", exception.
text());
1579 return EXIT_FAILURE;
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;
1587 return EXIT_SUCCESS;
constexpr int BLOCK_SPACING
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.
Exception thrown by Argz::proc() on unrecognised or malformed options.
void proc() override
Execute one processing/update step.
void event(SDL_Event &event) override
Handle one SDL event.
~PuzzleDropWindow() override
PuzzleDropWindow(const Arguments &args, const FramebufferDimensions &framebuffer)
void draw_block(BlockType type, float x, float y, float z, float half_extent, const mxvk::vec4D &tint)
void draw_text(TTF_Font *font, const std::string &text, int x, int y, const SDL_Color &color)
void draw_wildcard(float x, float y, float z, float half_extent, const mxvk::vec4D &color)
SDL_Surface * surface() const
void begin_frame(bool show_intro)
void draw_rectangle(int left, int top, int width, int height, mxvk::MXCOLOR color)
void draw_solid_cube(float x, float y, float z, float half_extent, mxvk::MXCOLOR color)
void set_view(float yaw, float pitch, float distance)
void resolve_multisampling()
void draw_block_image(BlockType type, int left, int top, int width, int height)
SoftwareRenderer(int width, int height, const std::string &data_root, bool enable_warp_fix, bool enable_mipmapping, float mip_bias)
void operator()(SDL_Surface *surface) const
Small RAII wrapper for an SDL_ttf font handle.
Four-by-four homogeneous transform matrix.
Main Vulkan window wrapper for MXVK.
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.
VkExtent2D swapchain_extent
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
void exit()
Request loop termination.
VK_Window()=default
Construct an empty window object.
Two-dimensional float vector with common arithmetic helpers.
Four-dimensional float vector used for homogeneous 3D coordinates.
void Normalize()
Normalize the 3D components in place and reset W to 1.
#define math3d_puzzle_drop_ASSET_DIR
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
PNG image loading and saving utilities via SDL3.
constexpr float ZOOM_SPEED
constexpr int piece_height
constexpr int LEVEL_COUNT
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
constexpr float FRAME_GAP
int texture_index(BlockType type)
constexpr float CAMERA_DISTANCE
constexpr int DEFAULT_FRAME_WIDTH
constexpr float FRAME_HALF_EXTENT
Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps)
constexpr float BLOCK_SPACING
constexpr std::array< const char *, 10 > BLOCK_TEXTURE_FILES
constexpr std::array< float, 3 > FALL_SECONDS
constexpr int BOARD_WIDTH
constexpr int DEFAULT_FRAME_HEIGHT
constexpr float BLOCK_HALF_EXTENT
void build_mipmaps(Texture &texture)
bool same_or_match(BlockType actual, BlockType expected)
constexpr int BOARD_HEIGHT
bool is_play_block(BlockType type)
Utilities for loading and saving PNG images.
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
void BuildTables()
Rebuild the sine and cosine lookup tables.
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
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.
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
bool framebufferSpecified
Whether --framebuffer was provided.
Parsed software framebuffer dimensions.
int width
Software framebuffer width in pixels.
int height
Software framebuffer height in pixels.
void new_piece(int start_x, int start_y, std::mt19937 &rng)
std::array< Block, 3 > blocks
void shift(ShiftDirection direction)
std::vector< mxvk::MXCOLOR > pixels
std::vector< TextureLevel > mipmaps
mxvk::MXCOLOR sample_filtered(float u, float v, float lod) const
std::vector< mxvk::MXCOLOR > pixels