4#if defined(MXVK_USE_EIGEN_MATH)
27#ifndef math3d_puzzle_drop_ASSET_DIR
28#define math3d_puzzle_drop_ASSET_DIR "."
56 class SurfaceDeleter {
61 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
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)};
103 blocks[0].type = types[2];
104 blocks[1].type = types[0];
105 blocks[2].type = types[1];
107 blocks[0].type = types[1];
108 blocks[1].type = types[2];
109 blocks[2].type = types[0];
164 [[nodiscard]]
static BlockType random_type(std::mt19937 &rng) {
166 return static_cast<BlockType>(distribution(rng));
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);
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);
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); };
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) {
238 level.
width = std::max(1, source_width / 2);
239 level.
height = std::max(1, source_height / 2);
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)];
258 level.
pixels[
static_cast<std::size_t
>(y * level.
width + x)] = ((alpha / 4U) << 24U) | ((red / 4U) << 16U) | ((green / 4U) << 8U) | (blue / 4U);
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;
271 throw mxvk::Exception(std::format(
"3dmath_puzzle_drop: failed to load PNG '{}'", path));
273 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
275 throw mxvk::Exception(std::format(
"3dmath_puzzle_drop: failed to convert PNG '{}': {}", path, SDL_GetError()));
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));
283 texture.
width = rgba->w;
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);
298 if (generate_mipmaps) {
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()));
317 block_textures.push_back(
load_texture(data_root +
"/" + filename, mipmapping_enabled));
321 [[nodiscard]] SDL_Surface *
surface()
const {
return frame_surface.get(); }
323 [[nodiscard]]
int width()
const {
return frame_width; }
325 [[nodiscard]]
int height()
const {
return frame_height; }
327 void set_view(
float yaw,
float pitch,
float distance) {
328 camera_rotation.BuildXYZ(pitch, yaw, 0.0f);
329 camera_distance = distance;
333 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
334 draw_flat_image(show_intro ? intro : background);
336 fill_translucent_rectangle(0, 0, frame_width, frame_height,
mxvk::MXVK_RGB(3, 8, 16), 150);
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);
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;
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);
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); }
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);
379 draw_cube(
nullptr, x, y, z, half_extent, tint);
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);
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);
411 blend_pixel(row +
static_cast<std::size_t
>(x * 4), color);
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()) {
421 SurfacePtr text_surface(TTF_RenderText_Blended(font, text.c_str(), 0, color));
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);
433 int frame_height = 0;
434 std::vector<float> depth_buffer;
435 std::vector<mxvk::MXCOLOR> color_buffer;
438 std::vector<Texture> block_textures;
439 bool warp_fix_enabled =
true;
440 bool mipmapping_enabled =
true;
441 float mip_level_bias = 0.0f;
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{{
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},
464 static constexpr std::array<std::array<int, 4>, 6> CUBE_FACES{{
473 static const std::array<std::array<mxvk::vec2D, 4>, 6> CUBE_FACE_UVS;
475 static void write_pixel(std::uint8_t *pixel,
mxvk::MXCOLOR color) {
482 static void blend_pixel(std::uint8_t *pixel,
mxvk::MXCOLOR 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);
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);
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);
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);
520 static_cast<float>(frame_width) * 0.43f + point.
x / z * scale,
521 static_cast<float>(frame_height) * 0.50f - point.
y / z * scale,
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);
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));
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) {
551 mxvk::vec4D normalized_light = light_direction;
553 float intensity = std::clamp(0.40f + std::max(0.0f, normal.DotProduct(normalized_light)) * 0.60f, 0.0f, 1.0f);
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;
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);
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);
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;
604 const float minification = std::max({
605 texels_per_pixel(a, b),
606 texels_per_pixel(b, c),
607 texels_per_pixel(c, a),
610 texture_lod = std::max(0.0f, std::log2(minification) + mip_level_bias);
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]);
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))) {
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]) {
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;
645 if (passing_samples == 0) {
649 const mxvk::vec2D shading_point(centroid_x /
static_cast<float>(passing_sample_count), centroid_y /
static_cast<float>(passing_sample_count));
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;
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);
667 for (std::size_t sample = 0; sample < MSAA_SAMPLE_COUNT; ++sample) {
668 if ((passing_samples &
static_cast<std::uint8_t
>(1U << sample)) == 0) {
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;
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}}},
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))))) {
694 std::random_device random_device;
695 rng.seed(random_device());
696 try_open_first_gamepad();
698 intro_start = std::chrono::steady_clock::now();
704 if (
event.type == SDL_EVENT_QUIT) {
709 if (
event.type == SDL_EVENT_GAMEPAD_ADDED) {
710 if (!open_gamepad(
event.gdevice.which)) {
711 try_open_first_gamepad();
716 if (
event.type == SDL_EVENT_GAMEPAD_REMOVED) {
717 if (gamepad !=
nullptr &&
event.gdevice.which == gamepad_id) {
719 try_open_first_gamepad();
724 if (
event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
725 handle_gamepad_button_down(
event.gbutton.button);
729 if (
event.type != SDL_EVENT_KEY_DOWN ||
event.key.repeat) {
732 if (intro_active && (
event.key.key == SDLK_SPACE ||
event.key.key == SDLK_RETURN ||
event.key.key == SDLK_KP_ENTER)) {
736 switch (
event.key.key) {
750 difficulty =
static_cast<int>(
event.key.key - SDLK_1);
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) {
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);
781 handle_gamepad_input(delta_seconds);
784 if (game_started && !game_over) {
785 if (std::chrono::duration<float>(now - last_fall).count() >=
FALL_SECONDS[
static_cast<std::size_t
>(difficulty)]) {
789 if (std::chrono::duration<float>(now - last_process).count() >= 0.018f) {
798 ensure_frame_sprite();
799 frame_sprite->updateTexture(renderer.surface());
802 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
806 std::string data_root;
811 std::array<std::array<Cell, BOARD_WIDTH>, BOARD_HEIGHT> board{};
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;
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;
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;
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)); }
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()})))); }
858 void ensure_frame_sprite() {
859 if (frame_sprite !=
nullptr) {
864 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
867 void draw_interface() {
868 const SDL_Color primary{255, 244, 223, 255};
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);
875 renderer.draw_text(ui_font.get(), std::format(
"Level {} Lines {} Difficulty {}", level, lines, difficulty + 1), scaled(24), scaled(22), primary);
877 draw_next_piece_preview();
880 void draw_next_piece_preview() {
881 if (!game_started || intro_active || game_over) {
885 const int panel_size = std::min({
887 static_cast<int>(
static_cast<float>(renderer.width()) * 0.22f),
888 static_cast<int>(
static_cast<float>(renderer.height()) * 0.30f),
890 if (panel_size < scaled(72)) {
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);
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});
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);
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;
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);
933 void finish_intro() {
934 intro_active =
false;
936 const auto now = std::chrono::steady_clock::now();
939 last_input_update = now;
940 reset_held_piece_input();
941 reset_held_gamepad_input();
944 void randomize_wildcard_color() {
945 std::uniform_int_distribution<int> distribution(0, 254);
947 static_cast<float>(distribution(rng)) / 255.0f,
948 static_cast<float>(distribution(rng)) / 255.0f,
949 static_cast<float>(distribution(rng)) / 255.0f,
955 renderer.set_view(grid_yaw, grid_pitch, camera_distance);
956 renderer.begin_frame(intro_active);
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;
967 renderer.draw_wildcard(block_x, block_y, z, BLOCK_HALF_EXTENT, wildcard_color);
970 renderer.draw_block(type, block_x, block_y, z, BLOCK_HALF_EXTENT, {1.0f, 1.0f, 1.0f, 1.0f});
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));
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));
985 const Cell &cell = board[
static_cast<std::size_t
>(y)][
static_cast<std::size_t
>(x)];
989 draw_cell(cell.type, x, y);
992 if (game_started && !game_over) {
993 for (
const Block &block : piece.blocks) {
994 draw_cell(block.type, block.x, block.y, -0.03f);
997 renderer.resolve_multisampling();
1000 void handle_view_controls(
const bool *keys,
float delta_seconds) {
1001 if (keys[SDL_SCANCODE_A]) {
1002 grid_yaw -= 115.0f * delta_seconds;
1004 if (keys[SDL_SCANCODE_D]) {
1005 grid_yaw += 115.0f * delta_seconds;
1007 if (keys[SDL_SCANCODE_W]) {
1008 grid_pitch = std::clamp(grid_pitch + 90.0f * delta_seconds, -70.0f, 70.0f);
1010 if (keys[SDL_SCANCODE_S]) {
1011 grid_pitch = std::clamp(grid_pitch - 90.0f * delta_seconds, -70.0f, 70.0f);
1013 if (keys[SDL_SCANCODE_PAGEUP]) {
1014 camera_distance = std::max(2.7f, camera_distance - 2.0f * delta_seconds);
1016 if (keys[SDL_SCANCODE_PAGEDOWN]) {
1017 camera_distance = std::min(7.0f, camera_distance + 2.0f * delta_seconds);
1021 void handle_piece_controls(
const bool *keys,
float delta_seconds) {
1022 if (!game_started || game_over) {
1023 reset_held_piece_input();
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;
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);
1041 horizontal_move_timer += delta_seconds;
1042 while (horizontal_move_timer >= 0.0f) {
1043 horizontal_move_timer -= REPEAT_SECONDS;
1044 move_piece_horizontal(direction);
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;
1055 last_fall = std::chrono::steady_clock::now();
1057 soft_drop_timer += delta_seconds;
1058 while (soft_drop_timer >= SOFT_DROP_REPEAT_SECONDS) {
1059 soft_drop_timer -= SOFT_DROP_REPEAT_SECONDS;
1061 last_fall = std::chrono::steady_clock::now();
1065 soft_drop_held =
false;
1066 soft_drop_timer = 0.0f;
1069 if (keys[SDL_SCANCODE_UP]) {
1070 constexpr float CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
1071 constexpr float CYCLE_REPEAT_SECONDS = 0.11f;
1074 cycle_timer = -CYCLE_INITIAL_DELAY_SECONDS;
1075 cycle_piece_blocks();
1077 cycle_timer += delta_seconds;
1078 while (cycle_timer >= 0.0f) {
1079 cycle_timer -= CYCLE_REPEAT_SECONDS;
1080 cycle_piece_blocks();
1089 void handle_gamepad_input(
float delta_seconds) {
1090 if (gamepad ==
nullptr || !game_started || game_over) {
1091 reset_held_gamepad_input();
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);
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);
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);
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;
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;
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) {
1134 last_fall = std::chrono::steady_clock::now();
1135 gamepad_soft_drop_repeat_timer = 0.0f;
1136 gamepad_soft_drop_held =
true;
1141 gamepad_cycle_held =
false;
1142 gamepad_cycle_repeat_timer = 0.0f;
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;
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;
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);
1161 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
1162 camera_distance = std::min(7.0f, camera_distance + ZOOM_SPEED * delta_seconds);
1164 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
1165 camera_distance = std::max(2.7f, camera_distance - ZOOM_SPEED * delta_seconds);
1169 void handle_gamepad_button_down(Uint8 button) {
1171 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
1178 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
1180 game_started =
true;
1181 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
1187 if (!game_started) {
1191 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
1193 }
else if (button == SDL_GAMEPAD_BUTTON_WEST) {
1195 }
else if (button == SDL_GAMEPAD_BUTTON_EAST) {
1197 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
1202 void reset_held_piece_input() {
1203 horizontal_move_timer = 0.0f;
1204 soft_drop_timer = 0.0f;
1206 horizontal_move_direction = 0;
1207 soft_drop_held =
false;
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;
1221 void move_piece_horizontal(
int direction) {
1222 if (!check_piece(piece, direction, 0)) {
1225 if (direction < 0) {
1235 if (!game_started || game_over) {
1238 while (check_piece(piece, 0, 1)) {
1242 last_fall = std::chrono::steady_clock::now();
1245 bool open_gamepad(SDL_JoystickID
id) {
1246 if (gamepad !=
nullptr && gamepad_id ==
id) {
1250 gamepad = SDL_OpenGamepad(
id);
1251 if (gamepad ==
nullptr) {
1258 void close_gamepad() {
1259 if (gamepad !=
nullptr) {
1260 SDL_CloseGamepad(gamepad);
1266 void try_open_first_gamepad() {
1267 if (gamepad !=
nullptr) {
1271 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1272 if (ids ==
nullptr || count <= 0) {
1273 if (ids !=
nullptr) {
1278 open_gamepad(ids[0]);
1283 reset_held_piece_input();
1284 reset_held_gamepad_input();
1285 for (
auto &row : board) {
1286 for (Cell &cell : row) {
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;
1300 if (check_piece(piece, 0, 1)) {
1306 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
1307 if (!check_piece(piece, 0, 0)) {
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) {
1319 const BlockType type = board[
static_cast<std::size_t
>(y)][
static_cast<std::size_t
>(x)].type;
1328 for (
const Block &block : piece.blocks) {
1329 if (block.x < 0 || block.x >= BOARD_WIDTH || block.y < 0 || block.y >= BOARD_HEIGHT) {
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;
1342 void rotate_left() {
1343 if (!game_started || game_over) {
1346 Piece test_piece = piece;
1347 test_piece.rotate_left();
1348 if (check_piece(test_piece, 0, 0)) {
1353 void rotate_right() {
1354 if (!game_started || game_over) {
1357 Piece test_piece = piece;
1358 test_piece.rotate_right();
1359 if (check_piece(test_piece, 0, 0)) {
1364 bool proc_blocks() {
1365 constexpr std::array<std::array<int, 2>, 4> DIRECTIONS{{
1374 for (
const auto &direction : DIRECTIONS) {
1375 for (BlockType start : COLOR_STARTS) {
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]);
1391 bool proc_move_down() {
1392 for (
int y = BOARD_HEIGHT - 2; y >= 0; --y) {
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)];
1403 bool updated =
false;
1404 for (
auto &row : board) {
1405 for (Cell &cell : row) {
1408 ++cell.flash_counter;
1409 if (cell.clear_value > 50) {
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); }
1421 [[nodiscard]]
bool check_block(
int x,
int y, BlockType expected)
const {
1422 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
1425 return same_or_match(board[
static_cast<std::size_t
>(y)][
static_cast<std::size_t
>(x)].type, expected);
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)];
1432 cell.clear_value = 1;
1433 cell.flash_counter = 0;
1439 if ((lines % 6) == 0 && level < LEVEL_COUNT) {
1453 std::cerr << std::format(
"mxvk: Exception: {}\n", exception.
text());
1454 return EXIT_FAILURE;
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;
1462 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