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