MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
4#if defined(MXVK_USE_EIGEN_MATH)
6#else
7#include "mxvk/mxvk_math.h"
8#endif
9
10#include <SDL3/SDL.h>
11
12#include <algorithm>
13#include <array>
14#include <chrono>
15#include <cmath>
16#include <cstdint>
17#include <cstdlib>
18#include <format>
19#include <iostream>
20#include <limits>
21#include <memory>
22#include <random>
23#include <vector>
24
25namespace {
26 constexpr int DEFAULT_FRAME_WIDTH = 320;
27 constexpr int DEFAULT_FRAME_HEIGHT = 240;
28 constexpr int WINDOW_WIDTH = 1440;
29 constexpr int WINDOW_HEIGHT = 1080;
30 constexpr float COURT_HALF_WIDTH = 2.65f;
31 constexpr float COURT_HALF_HEIGHT = 1.45f;
32 constexpr float PADDLE_X = 2.25f;
33 constexpr float PADDLE_HALF_WIDTH = 0.12f;
34 constexpr float PADDLE_HALF_HEIGHT = 0.42f;
35 constexpr float BALL_RADIUS = 0.14f;
36 constexpr float CAMERA_DISTANCE = 7.0f;
37
38 class SurfaceDeleter {
39 public:
40 void operator()(SDL_Surface *surface) const {
41 SDL_DestroySurface(surface);
42 }
43 };
44
45 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
46
47 struct Triangle {
48 std::array<std::size_t, 3> indices{};
49 };
50
51 struct Mesh {
52 std::vector<mxvk::vec4D> vertices;
53 std::vector<Triangle> triangles;
54 bool two_sided = false;
55
56 [[nodiscard]] static Mesh cube() {
57 return {
58 {
59 {-1.0f, -1.0f, -1.0f, 1.0f},
60 {1.0f, -1.0f, -1.0f, 1.0f},
61 {1.0f, 1.0f, -1.0f, 1.0f},
62 {-1.0f, 1.0f, -1.0f, 1.0f},
63 {-1.0f, -1.0f, 1.0f, 1.0f},
64 {1.0f, -1.0f, 1.0f, 1.0f},
65 {1.0f, 1.0f, 1.0f, 1.0f},
66 {-1.0f, 1.0f, 1.0f, 1.0f},
67 },
68 {
69 {{0, 3, 2}},
70 {{0, 2, 1}},
71 {{4, 5, 6}},
72 {{4, 6, 7}},
73 {{0, 4, 7}},
74 {{0, 7, 3}},
75 {{1, 2, 6}},
76 {{1, 6, 5}},
77 {{3, 7, 6}},
78 {{3, 6, 2}},
79 {{0, 1, 5}},
80 {{0, 5, 4}},
81 },
82 };
83 }
84
85 [[nodiscard]] static Mesh sphere(int latitude_segments, int longitude_segments) {
86 Mesh mesh;
87 mesh.two_sided = true;
88 for (int latitude = 0; latitude <= latitude_segments; ++latitude) {
89 const float phi = static_cast<float>(latitude) * mxvk::PI / static_cast<float>(latitude_segments);
90 const float ring_radius = std::sin(phi);
91 const float y = std::cos(phi);
92 for (int longitude = 0; longitude <= longitude_segments; ++longitude) {
93 const float theta = static_cast<float>(longitude) * 2.0f * mxvk::PI / static_cast<float>(longitude_segments);
94 mesh.vertices.emplace_back(
95 ring_radius * std::cos(theta),
96 y,
97 ring_radius * std::sin(theta),
98 1.0f);
99 }
100 }
101
102 const std::size_t row_size = static_cast<std::size_t>(longitude_segments + 1);
103 for (int latitude = 0; latitude < latitude_segments; ++latitude) {
104 for (int longitude = 0; longitude < longitude_segments; ++longitude) {
105 const std::size_t first =
106 static_cast<std::size_t>(latitude) * row_size +
107 static_cast<std::size_t>(longitude);
108 const std::size_t second = first + row_size;
109 mesh.triangles.push_back({{first, second, first + 1}});
110 mesh.triangles.push_back({{first + 1, second, second + 1}});
111 }
112 }
113 return mesh;
114 }
115 };
116
124
126 public:
128 : frame_width(width),
129 frame_height(height),
130 depth_buffer(static_cast<std::size_t>(width) * static_cast<std::size_t>(height)) {
131 frame_surface.reset(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
132 if (frame_surface == nullptr) {
133 throw mxvk::Exception(std::format("3dmath_pong: failed to create framebuffer: {}", SDL_GetError()));
134 }
135 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
136 if (frame_format == nullptr) {
137 throw mxvk::Exception(std::format("3dmath_pong: failed to query framebuffer format: {}", SDL_GetError()));
138 }
139 camera_rotation.BuildXYZ(17.0f, -8.0f, 0.0f);
140 }
141
142 [[nodiscard]] SDL_Surface *surface() const {
143 return frame_surface.get();
144 }
145
146 [[nodiscard]] int width() const {
147 return frame_width;
148 }
149
150 [[nodiscard]] int height() const {
151 return frame_height;
152 }
153
154 void begin_frame() {
155 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
156 for (int y = 0; y < frame_height; ++y) {
157 const float fraction = static_cast<float>(y) / static_cast<float>(frame_height - 1);
158 const int red = static_cast<int>(4.0f + fraction * 5.0f);
159 const int green = static_cast<int>(10.0f + fraction * 12.0f);
160 const int blue = static_cast<int>(24.0f + fraction * 20.0f);
161 const mxvk::MXCOLOR color = mxvk::MXVK_RGB(red, green, blue);
162 for (int x = 0; x < frame_width; ++x) {
163 put_pixel(x, y, color);
164 }
165 }
166 }
167
168 void draw_mesh(const MeshInstance &instance) {
169 mxvk::Mat4D object_rotation;
170 object_rotation.BuildXYZ(instance.rotation.x, instance.rotation.y, instance.rotation.z);
171
172 std::vector<mxvk::vec4D> camera_vertices(instance.mesh.vertices.size());
173 std::vector<mxvk::vec4D> projected_vertices(instance.mesh.vertices.size());
174 for (std::size_t index = 0; index < instance.mesh.vertices.size(); ++index) {
175 const mxvk::vec4D &vertex = instance.mesh.vertices[index];
176 mxvk::vec4D transformed(
177 vertex.x * instance.scale.x,
178 vertex.y * instance.scale.y,
179 vertex.z * instance.scale.z,
180 1.0f);
181 transformed = object_rotation.MulVec(transformed);
182 transformed += instance.position;
183 transformed = camera_rotation.MulVec(transformed);
184 transformed.z += CAMERA_DISTANCE;
185 camera_vertices[index] = transformed;
186 projected_vertices[index] = project(transformed);
187 }
188
189 const mxvk::vec4D light_direction = normalized({-0.35f, -0.65f, -1.0f, 0.0f});
190 for (const Triangle &triangle : instance.mesh.triangles) {
191 const mxvk::vec4D &a = camera_vertices[triangle.indices[0]];
192 const mxvk::vec4D &b = camera_vertices[triangle.indices[1]];
193 const mxvk::vec4D &c = camera_vertices[triangle.indices[2]];
194 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
195 normal.Normalize();
196
197 const mxvk::vec4D center = (a + b + c) * (1.0f / 3.0f);
198 const mxvk::vec4D view_direction(-center.x, -center.y, -center.z, 0.0f);
199 if (normal.DotProduct(view_direction) <= 0.0f) {
200 if (!instance.mesh.two_sided) {
201 continue;
202 }
203 normal = normal * -1.0f;
204 }
205
206 const float diffuse = std::max(0.0f, normal.DotProduct(light_direction));
207 const float intensity = std::clamp(0.32f + diffuse * 0.68f, 0.0f, 1.0f);
208 rasterize_triangle(
209 projected_vertices[triangle.indices[0]],
210 projected_vertices[triangle.indices[1]],
211 projected_vertices[triangle.indices[2]],
212 mxvk::shade_color(instance.color, intensity));
213 }
214 }
215
216 void draw_digit(int x, int y, int digit, int scale, mxvk::MXCOLOR color) {
217 static constexpr std::array<std::uint8_t, 10> SEGMENTS = {
218 0b1111110,
219 0b0110000,
220 0b1101101,
221 0b1111001,
222 0b0110011,
223 0b1011011,
224 0b1011111,
225 0b1110000,
226 0b1111111,
227 0b1111011,
228 };
229 const std::uint8_t segments = SEGMENTS[static_cast<std::size_t>(std::clamp(digit, 0, 9))];
230 const auto horizontal = [this, scale, color](int left, int top) {
231 fill_rectangle(left + scale, top, scale * 3, scale, color);
232 };
233 const auto vertical = [this, scale, color](int left, int top) {
234 fill_rectangle(left, top + scale, scale, scale * 3, color);
235 };
236 if ((segments & 0b1000000U) != 0U)
237 horizontal(x, y);
238 if ((segments & 0b0100000U) != 0U)
239 vertical(x + scale * 4, y);
240 if ((segments & 0b0010000U) != 0U)
241 vertical(x + scale * 4, y + scale * 4);
242 if ((segments & 0b0001000U) != 0U)
243 horizontal(x, y + scale * 7);
244 if ((segments & 0b0000100U) != 0U)
245 vertical(x, y + scale * 4);
246 if ((segments & 0b0000010U) != 0U)
247 vertical(x, y);
248 if ((segments & 0b0000001U) != 0U)
249 horizontal(x, y + scale * 3);
250 }
251
253 fill_rectangle(frame_width - 17, 8, 3, 12, mxvk::MXVK_RGB(255, 220, 92));
254 fill_rectangle(frame_width - 10, 8, 3, 12, mxvk::MXVK_RGB(255, 220, 92));
255 }
256
257 private:
258 SurfacePtr frame_surface;
259 const SDL_PixelFormatDetails *frame_format = nullptr;
260 int frame_width = 0;
261 int frame_height = 0;
262 std::vector<float> depth_buffer;
263 mxvk::Mat4D camera_rotation;
264
265 [[nodiscard]] static mxvk::vec4D normalized(mxvk::vec4D value) {
266 value.Normalize();
267 return value;
268 }
269
270 [[nodiscard]] std::uint32_t map_color(mxvk::MXCOLOR color) const {
271 return SDL_MapRGBA(
272 frame_format,
273 nullptr,
274 mxvk::color_r(color),
275 mxvk::color_g(color),
276 mxvk::color_b(color),
277 mxvk::color_a(color));
278 }
279
280 void put_pixel(int x, int y, mxvk::MXCOLOR color) {
281 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
282 return;
283 }
284 auto *row =
285 static_cast<std::uint8_t *>(frame_surface->pixels) +
286 static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_surface->pitch);
287 *(reinterpret_cast<std::uint32_t *>(row) + x) = map_color(color);
288 }
289
290 void fill_rectangle(int x, int y, int width, int height, mxvk::MXCOLOR color) {
291 for (int row = 0; row < height; ++row) {
292 for (int column = 0; column < width; ++column) {
293 put_pixel(x + column, y + row, color);
294 }
295 }
296 }
297
298 [[nodiscard]] mxvk::vec4D project(const mxvk::vec4D &point) const {
299 const float scale = static_cast<float>(std::min(frame_width, frame_height)) * 1.62f;
300 const float z = std::max(point.z, 0.001f);
301 return {
302 static_cast<float>(frame_width) * 0.5f + point.x / z * scale,
303 static_cast<float>(frame_height) * 0.54f - point.y / z * scale,
304 point.z,
305 1.0f,
306 };
307 }
308
309 void rasterize_triangle(const mxvk::vec4D &a, const mxvk::vec4D &b, const mxvk::vec4D &c, mxvk::MXCOLOR color) {
310 const auto edge = [](const mxvk::vec4D &first, const mxvk::vec4D &second, float x, float y) {
311 return (x - first.x) * (second.y - first.y) - (y - first.y) * (second.x - first.x);
312 };
313 const float area = edge(b, c, a.x, a.y);
314 if (std::abs(area) <= mxvk::EPSILON) {
315 return;
316 }
317
318 const int min_x = std::clamp(static_cast<int>(std::floor(std::min({a.x, b.x, c.x}))), 0, frame_width - 1);
319 const int max_x = std::clamp(static_cast<int>(std::ceil(std::max({a.x, b.x, c.x}))), 0, frame_width - 1);
320 const int min_y = std::clamp(static_cast<int>(std::floor(std::min({a.y, b.y, c.y}))), 0, frame_height - 1);
321 const int max_y = std::clamp(static_cast<int>(std::ceil(std::max({a.y, b.y, c.y}))), 0, frame_height - 1);
322 for (int y = min_y; y <= max_y; ++y) {
323 for (int x = min_x; x <= max_x; ++x) {
324 const float sample_x = static_cast<float>(x) + 0.5f;
325 const float sample_y = static_cast<float>(y) + 0.5f;
326 const float weight_a = edge(b, c, sample_x, sample_y) / area;
327 const float weight_b = edge(c, a, sample_x, sample_y) / area;
328 const float weight_c = edge(a, b, sample_x, sample_y) / area;
329 if (weight_a < 0.0f || weight_b < 0.0f || weight_c < 0.0f) {
330 continue;
331 }
332 const float reciprocal_depth = weight_a / a.z + weight_b / b.z + weight_c / c.z;
333 if (reciprocal_depth <= mxvk::EPSILON) {
334 continue;
335 }
336 const float depth = 1.0f / reciprocal_depth;
337 const std::size_t pixel_index =
338 static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_width) +
339 static_cast<std::size_t>(x);
340 if (depth >= depth_buffer[pixel_index]) {
341 continue;
342 }
343 depth_buffer[pixel_index] = depth;
344 put_pixel(x, y, color);
345 }
346 }
347 }
348 };
349
350 class PongGame {
351 public:
353 : random_engine(std::random_device{}()) {
354 reset();
355 }
356
357 void reset() {
358 player_y = 0.0f;
359 computer_y = 0.0f;
360 player_score = 0;
361 computer_score = 0;
362 paused = false;
363 reset_ball(random_direction());
364 }
365
367 paused = !paused;
368 }
369
370 void set_player_position(float position) {
371 player_y = std::clamp(position, paddle_minimum_y(), paddle_maximum_y());
372 }
373
374 void move_player(float movement) {
375 set_player_position(player_y + movement);
376 }
377
378 void update(float delta_seconds) {
379 if (paused) {
380 return;
381 }
382
383 const float target = ball_position.y;
384 const float difference = target - computer_y;
385 const float computer_movement = std::clamp(difference, -AI_SPEED * delta_seconds, AI_SPEED * delta_seconds);
386 computer_y = std::clamp(computer_y + computer_movement, paddle_minimum_y(), paddle_maximum_y());
387
388 ball_position += ball_velocity * delta_seconds;
389 if (ball_position.y + BALL_RADIUS >= COURT_HALF_HEIGHT) {
390 ball_position.y = COURT_HALF_HEIGHT - BALL_RADIUS;
391 ball_velocity.y = -std::abs(ball_velocity.y);
392 } else if (ball_position.y - BALL_RADIUS <= -COURT_HALF_HEIGHT) {
393 ball_position.y = -COURT_HALF_HEIGHT + BALL_RADIUS;
394 ball_velocity.y = std::abs(ball_velocity.y);
395 }
396
397 collide_with_paddle(-PADDLE_X, player_y, 1.0f);
398 collide_with_paddle(PADDLE_X, computer_y, -1.0f);
399
400 if (ball_position.x < -COURT_HALF_WIDTH - BALL_RADIUS) {
401 ++computer_score;
402 reset_ball(1.0f);
403 } else if (ball_position.x > COURT_HALF_WIDTH + BALL_RADIUS) {
404 ++player_score;
405 reset_ball(-1.0f);
406 }
407 }
408
409 [[nodiscard]] float player_position() const { return player_y; }
410 [[nodiscard]] float computer_position() const { return computer_y; }
411 [[nodiscard]] const mxvk::vec4D &ball() const { return ball_position; }
412 [[nodiscard]] int left_score() const { return player_score; }
413 [[nodiscard]] int right_score() const { return computer_score; }
414 [[nodiscard]] bool is_paused() const { return paused; }
415
416 private:
417 static constexpr float AI_SPEED = 1.65f;
418 float player_y = 0.0f;
419 float computer_y = 0.0f;
420 mxvk::vec4D ball_position{0.0f, 0.0f, -0.18f, 1.0f};
421 mxvk::vec4D ball_velocity{1.8f, 0.35f, 0.0f, 0.0f};
422 int player_score = 0;
423 int computer_score = 0;
424 bool paused = false;
425 std::mt19937 random_engine;
426
427 [[nodiscard]] static float paddle_minimum_y() {
428 return -COURT_HALF_HEIGHT + PADDLE_HALF_HEIGHT + 0.08f;
429 }
430
431 [[nodiscard]] static float paddle_maximum_y() {
432 return COURT_HALF_HEIGHT - PADDLE_HALF_HEIGHT - 0.08f;
433 }
434
435 [[nodiscard]] float random_direction() {
436 std::uniform_int_distribution<int> distribution(0, 1);
437 return distribution(random_engine) == 0 ? -1.0f : 1.0f;
438 }
439
440 void reset_ball(float horizontal_direction) {
441 std::uniform_real_distribution<float> vertical_distribution(-0.62f, 0.62f);
442 ball_position = {0.0f, 0.0f, -0.18f, 1.0f};
443 ball_velocity = {horizontal_direction, vertical_distribution(random_engine), 0.0f, 0.0f};
444 ball_velocity.Normalize();
445 ball_velocity = ball_velocity * 1.8f;
446 }
447
448 void collide_with_paddle(float paddle_x, float paddle_y, float outgoing_direction) {
449 if (ball_velocity.x * outgoing_direction >= 0.0f) {
450 return;
451 }
452 const bool horizontal_overlap =
453 std::abs(ball_position.x - paddle_x) <= PADDLE_HALF_WIDTH + BALL_RADIUS;
454 const bool vertical_overlap =
455 std::abs(ball_position.y - paddle_y) <= PADDLE_HALF_HEIGHT + BALL_RADIUS;
456 if (!horizontal_overlap || !vertical_overlap) {
457 return;
458 }
459
460 ball_position.x = paddle_x + outgoing_direction * (PADDLE_HALF_WIDTH + BALL_RADIUS);
461 const float offset = (ball_position.y - paddle_y) / PADDLE_HALF_HEIGHT;
462 const float current_speed = std::min(3.5f, ball_velocity.Length() * 1.045f);
463 ball_velocity.x = outgoing_direction;
464 ball_velocity.y += offset * 0.72f;
465 ball_velocity.z = 0.0f;
466 ball_velocity.Normalize();
467 ball_velocity = ball_velocity * current_speed;
468 }
469 };
470} // namespace
471
472namespace example {
473 class Math3DPongWindow final : public mxvk::VK_Window {
474 public:
475 Math3DPongWindow(bool fullscreen, bool enable_vsync, const FramebufferDimensions &framebuffer)
476 : mxvk::VK_Window("MXVK 3D Math Pong", WINDOW_WIDTH, WINDOW_HEIGHT, fullscreen, MXVK_VALIDATION, enable_vsync),
477 renderer(framebuffer.width, framebuffer.height),
478 cube_mesh(Mesh::cube()),
479 ball_mesh(Mesh::sphere(8, 12)) {
480 setClearColor(0.01f, 0.02f, 0.04f, 1.0f);
482 }
483
484 void event(SDL_Event &event) override {
485 if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
486 switch (event.key.key) {
487 case SDLK_ESCAPE:
488 exit();
489 break;
490 case SDLK_SPACE:
491 game.toggle_pause();
492 break;
493 case SDLK_R:
494 game.reset();
495 break;
496 default:
497 break;
498 }
499 }
500 if (event.type == SDL_EVENT_MOUSE_MOTION) {
501 const float normalized =
502 1.0f - 2.0f * event.motion.y / static_cast<float>(std::max(1, output_height));
503 game.set_player_position(normalized * COURT_HALF_HEIGHT);
504 }
505 }
506
507 void proc() override {
508 output_width = swapchain_extent.width > 0U ? static_cast<int>(swapchain_extent.width) : WINDOW_WIDTH;
509 output_height = swapchain_extent.height > 0U ? static_cast<int>(swapchain_extent.height) : WINDOW_HEIGHT;
510 ensure_sprite();
511 update_game();
512 draw_game();
513 frame_sprite->updateTexture(renderer.surface());
514 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
515 }
516
517 private:
518 SoftwareRenderer renderer;
519 Mesh cube_mesh;
520 Mesh ball_mesh;
521 PongGame game;
522 mxvk::VK_Sprite *frame_sprite = nullptr;
523 std::chrono::steady_clock::time_point previous_frame_time = std::chrono::steady_clock::now();
524 int output_width = WINDOW_WIDTH;
525 int output_height = WINDOW_HEIGHT;
526
527 void ensure_sprite() {
528 if (frame_sprite != nullptr) {
529 return;
530 }
531 frame_sprite = createSprite(renderer.surface());
532 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
533 }
534
535 void update_game() {
536 const auto now = std::chrono::steady_clock::now();
537 const float delta_seconds = std::min(
538 std::chrono::duration<float>(now - previous_frame_time).count(),
539 0.05f);
540 previous_frame_time = now;
541
542 const bool *keyboard = SDL_GetKeyboardState(nullptr);
543 if (keyboard != nullptr) {
544 float movement = 0.0f;
545 if (keyboard[SDL_SCANCODE_W] || keyboard[SDL_SCANCODE_UP]) {
546 movement += 2.7f * delta_seconds;
547 }
548 if (keyboard[SDL_SCANCODE_S] || keyboard[SDL_SCANCODE_DOWN]) {
549 movement -= 2.7f * delta_seconds;
550 }
551 game.move_player(movement);
552 }
553 game.update(delta_seconds);
554 }
555
556 void draw_game() {
557 renderer.begin_frame();
558 draw_cuboid({0.0f, 0.0f, 0.28f, 1.0f}, {2.72f, 1.52f, 0.10f, 0.0f}, mxvk::MXVK_RGB(12, 38, 65));
559 draw_cuboid({0.0f, COURT_HALF_HEIGHT + 0.07f, 0.08f, 1.0f}, {2.72f, 0.07f, 0.12f, 0.0f}, mxvk::MXVK_RGB(45, 198, 255));
560 draw_cuboid({0.0f, -COURT_HALF_HEIGHT - 0.07f, 0.08f, 1.0f}, {2.72f, 0.07f, 0.12f, 0.0f}, mxvk::MXVK_RGB(45, 198, 255));
561
562 for (int dash = -4; dash <= 4; ++dash) {
563 draw_cuboid(
564 {0.0f, static_cast<float>(dash) * 0.31f, 0.11f, 1.0f},
565 {0.025f, 0.09f, 0.025f, 0.0f},
566 mxvk::MXVK_RGB(112, 151, 180));
567 }
568
569 draw_cuboid(
570 {-PADDLE_X, game.player_position(), -0.02f, 1.0f},
572 mxvk::MXVK_RGB(30, 144, 255));
573 draw_cuboid(
574 {PADDLE_X, game.computer_position(), -0.02f, 1.0f},
576 mxvk::MXVK_RGB(255, 65, 112));
577
578 const float rotation = static_cast<float>(SDL_GetTicks()) * 0.18f;
579 renderer.draw_mesh({
580 ball_mesh,
581 game.ball(),
583 {rotation, rotation * 0.7f, 0.0f, 0.0f},
584 mxvk::MXVK_RGB(255, 236, 125),
585 });
586
587 const int score_scale = std::max(2, std::min(renderer.width(), renderer.height()) / 80);
588 const int score_y = score_scale * 3;
589 renderer.draw_digit(
590 renderer.width() / 2 - score_scale * 10,
591 score_y,
592 game.left_score() % 10,
593 score_scale,
594 mxvk::MXVK_RGB(80, 183, 255));
595 renderer.draw_digit(
596 renderer.width() / 2 + score_scale * 4,
597 score_y,
598 game.right_score() % 10,
599 score_scale,
600 mxvk::MXVK_RGB(255, 91, 133));
601 if (game.is_paused()) {
602 renderer.draw_pause_indicator();
603 }
604 }
605
606 void draw_cuboid(const mxvk::vec4D &position, const mxvk::vec4D &scale, mxvk::MXCOLOR color) {
607 renderer.draw_mesh({
608 cube_mesh,
609 position,
610 scale,
611 {0.0f, 0.0f, 0.0f, 0.0f},
612 color,
613 });
614 }
615 };
616} // namespace example
617
618int main(int argc, char **argv) {
619 try {
620 const Arguments args = proc_args(argc, argv);
621 const FramebufferDimensions framebuffer = args.framebufferSpecified
622 ? args.framebuffer
624 example::Math3DPongWindow window(args.fullscreen, args.enable_vsync, framebuffer);
625 window.loop();
626 } catch (mxvk::Exception &exception) {
627 std::cerr << std::format("mxvk: Exception: {}\n", exception.text());
628 return EXIT_FAILURE;
629 } catch (ArgException<std::string> &exception) {
630 std::cerr << std::format("mxvk: Argument Exception: {}\n", exception.text());
631 return EXIT_FAILURE;
632 }
633 return EXIT_SUCCESS;
634}
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Definition argz.hpp:872
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:178
void set_player_position(float position)
Definition main.cpp:370
void update(float delta_seconds)
Definition main.cpp:378
void move_player(float movement)
Definition main.cpp:374
const mxvk::vec4D & ball() const
Definition main.cpp:411
void draw_mesh(const MeshInstance &instance)
Definition main.cpp:168
void draw_digit(int x, int y, int digit, int scale, mxvk::MXCOLOR color)
Definition main.cpp:216
void operator()(SDL_Surface *surface) const
Definition main.cpp:40
Math3DPongWindow(bool fullscreen, bool enable_vsync, const FramebufferDimensions &framebuffer)
Definition main.cpp:475
void event(SDL_Event &event) override
Handle one SDL event.
Definition main.cpp:484
void proc() override
Execute one processing/update step.
Definition main.cpp:507
std::string text() const
Four-by-four homogeneous transform matrix.
Definition mxvk_math.h:812
void BuildXYZ(float theta_x, float theta_y, float theta_z)
Build an XYZ Euler rotation matrix from angles in degrees.
Definition mxvk_math.h:973
vec4D MulVec(const vec4D &in) const
Transform a homogeneous 4D vector by this matrix.
Definition mxvk_math.h:881
void setTextureFilter(VkFilter filter)
Select the hardware filter used when scaling this sprite.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
void loop()
Run the main event/render loop.
Definition mxvk.cpp:600
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
Definition mxvk.cpp:3477
VkExtent2D swapchain_extent
Definition mxvk.hpp:495
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:593
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VK_Window()=default
Construct an empty window object.
Four-dimensional float vector used for homogeneous 3D coordinates.
Definition mxvk_math.h:416
float y
Y coordinate.
Definition mxvk_math.h:422
float x
X coordinate.
Definition mxvk_math.h:419
void Normalize()
Normalize the 3D components in place and reset W to 1.
Definition mxvk_math.h:518
constexpr float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
Definition mxvk_math.h:503
float z
Z coordinate.
Definition mxvk_math.h:425
void Build(const vec4D &to)
Replace this vector with the direction from this point to to.
Definition mxvk_math.h:544
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
Definition main.cpp:29
constexpr float BALL_RADIUS
Definition main.cpp:35
constexpr float COURT_HALF_HEIGHT
Definition main.cpp:31
constexpr float CAMERA_DISTANCE
Definition main.cpp:36
constexpr int WINDOW_HEIGHT
Definition main.cpp:29
constexpr int DEFAULT_FRAME_WIDTH
Definition main.cpp:26
constexpr float PADDLE_HALF_WIDTH
Definition main.cpp:33
constexpr int DEFAULT_FRAME_HEIGHT
Definition main.cpp:27
constexpr int WINDOW_WIDTH
Definition main.cpp:28
constexpr float PADDLE_X
Definition main.cpp:32
constexpr float COURT_HALF_WIDTH
Definition main.cpp:30
constexpr float PADDLE_HALF_HEIGHT
Definition main.cpp:34
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
Definition mxvk_math.h:54
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
Definition mxvk_math.h:40
void BuildTables()
Rebuild the sine and cosine lookup tables.
Definition mxvk_math.h:113
MXCOLOR shade_color(MXCOLOR color, float intensity)
Scale the RGB channels of a color while preserving alpha.
Definition mxvk_math.h:79
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
Definition mxvk_math.h:59
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
Definition mxvk_math.h:49
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
Definition mxvk_math.h:69
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
Definition mxvk_math.h:37
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
Definition mxvk_math.h:64
constexpr float PI
Mathematical constant pi as a single-precision value.
Definition mxvk_math.h:34
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:730
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
Definition argz.hpp:758
bool framebufferSpecified
Whether --framebuffer was provided.
Definition argz.hpp:759
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:736
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:750
Parsed software framebuffer dimensions.
Definition argz.hpp:721
std::vector< Triangle > triangles
Definition main.cpp:53
static Mesh sphere(int latitude_segments, int longitude_segments)
Definition main.cpp:85
std::vector< mxvk::vec4D > vertices
Definition main.cpp:52
std::array< std::size_t, 3 > indices
Definition main.cpp:48