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"
5#if defined(MXVK_USE_EIGEN_MATH)
7#else
8#include "mxvk/mxvk_math.h"
9#endif
10#include "mxvk/mxvk_png.hpp"
11
12#include <SDL3/SDL.h>
13
14#include <algorithm>
15#include <array>
16#include <cmath>
17#include <cstdint>
18#include <cstdlib>
19#include <filesystem>
20#include <format>
21#include <iostream>
22#include <limits>
23#include <memory>
24#include <string>
25#include <string_view>
26#include <unordered_map>
27#include <vector>
28
29namespace {
30#if defined(MXVK_OBJ_LOADER)
31 constexpr std::string_view APP_NAME = "3dmath_obj_loader";
32 constexpr std::string_view MODEL_FORMAT = "OBJ";
33 constexpr std::string_view DEFAULT_MODEL = "sphere.obj";
34 constexpr std::string_view WINDOW_TITLE = "MXVK 3D Math OBJ Loader";
35 constexpr float MODEL_FIT_RADIUS = 1.6f;
36 constexpr mxvk::MXCOLOR BACKGROUND_COLOR = mxvk::MXVK_RGB(15, 22, 35);
37#else
38 constexpr std::string_view APP_NAME = "3dmath_plg_loader";
39 constexpr std::string_view MODEL_FORMAT = "PLG";
40 constexpr std::string_view DEFAULT_MODEL = "sphere.plg";
41 constexpr std::string_view WINDOW_TITLE = "MXVK 3D Math PLG Loader";
42 constexpr float MODEL_SCALE = 2.0f;
44#endif
45
46 class SurfaceDeleter {
47 public:
48 void operator()(SDL_Surface *surface) const { SDL_DestroySurface(surface); }
49 };
50
51 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
52
53 [[nodiscard]] mxvk::MXCOLOR pack_color(std::uint32_t red, std::uint32_t green, std::uint32_t blue, std::uint32_t alpha) { return ((alpha & 0xFFU) << 24U) | ((red & 0xFFU) << 16U) | ((green & 0xFFU) << 8U) | (blue & 0xFFU); }
54
55 [[nodiscard]] mxvk::MXCOLOR interpolate_color(mxvk::MXCOLOR first, mxvk::MXCOLOR second, float fraction) {
56 const std::uint32_t second_weight = static_cast<std::uint32_t>(std::clamp(static_cast<int>(fraction * 256.0f + 0.5f), 0, 256));
57 const std::uint32_t first_weight = 256U - second_weight;
58 const auto interpolate_channel = [first_weight, second_weight](std::uint8_t first_channel, std::uint8_t second_channel) { return (static_cast<std::uint32_t>(first_channel) * first_weight + static_cast<std::uint32_t>(second_channel) * second_weight + 128U) >> 8U; };
59 return pack_color(interpolate_channel(mxvk::color_r(first), mxvk::color_r(second)), interpolate_channel(mxvk::color_g(first), mxvk::color_g(second)), interpolate_channel(mxvk::color_b(first), mxvk::color_b(second)), interpolate_channel(mxvk::color_a(first), mxvk::color_a(second)));
60 }
61
62 struct MipLevel {
63 int width = 0;
64 int height = 0;
65 std::vector<mxvk::MXCOLOR> pixels;
66
67 [[nodiscard]] mxvk::MXCOLOR sample_bilinear(float u, float v, bool repeat_horizontal) const {
68 u = repeat_horizontal ? u - std::floor(u) : std::clamp(u, 0.0f, 1.0f);
69 v = std::clamp(v, 0.0f, 1.0f);
70 const float texture_x = u * static_cast<float>(repeat_horizontal ? width : width - 1);
71 const float texture_y = v * static_cast<float>(height - 1);
72 const int first_x = static_cast<int>(std::floor(texture_x));
73 const int first_y = static_cast<int>(std::floor(texture_y));
74 const int second_x = repeat_horizontal ? (first_x + 1) % width : std::min(first_x + 1, width - 1);
75 const int second_y = std::min(first_y + 1, height - 1);
76 const float x_fraction = texture_x - static_cast<float>(first_x);
77 const float y_fraction = texture_y - static_cast<float>(first_y);
78 const auto texel = [this](int x, int y) { return pixels[static_cast<std::size_t>(y) * static_cast<std::size_t>(width) + static_cast<std::size_t>(x)]; };
79 const mxvk::MXCOLOR top = interpolate_color(texel(first_x, first_y), texel(second_x, first_y), x_fraction);
80 const mxvk::MXCOLOR bottom = interpolate_color(texel(first_x, second_y), texel(second_x, second_y), x_fraction);
81 return interpolate_color(top, bottom, y_fraction);
82 }
83 };
84
85 struct Texture {
86 std::vector<MipLevel> levels;
87
88 [[nodiscard]] bool empty() const { return levels.empty() || levels.front().width <= 0 || levels.front().height <= 0 || levels.front().pixels.empty(); }
89
90 [[nodiscard]] int width() const { return empty() ? 0 : levels.front().width; }
91
92 [[nodiscard]] int height() const { return empty() ? 0 : levels.front().height; }
93
94 [[nodiscard]] mxvk::MXCOLOR sample(float u, float v, float level, bool repeat_horizontal) const {
95 level = std::clamp(level, 0.0f, static_cast<float>(levels.size() - 1));
96 const std::size_t first_level = static_cast<std::size_t>(std::floor(level));
97 const std::size_t second_level = std::min(first_level + 1, levels.size() - 1);
98 const float fraction = level - static_cast<float>(first_level);
99 const mxvk::MXCOLOR first_color = levels[first_level].sample_bilinear(u, v, repeat_horizontal);
100 if (first_level == second_level) {
101 return first_color;
102 }
103 return interpolate_color(first_color, levels[second_level].sample_bilinear(u, v, repeat_horizontal), fraction);
104 }
105 };
106
107 [[nodiscard]] SurfacePtr create_frame_surface(int width, int height) {
108 SurfacePtr surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
109 if (!surface) {
110 throw mxvk::Exception(std::format("{}: failed to create frame surface: {}", APP_NAME, SDL_GetError()));
111 }
112 return surface;
113 }
114
115 [[nodiscard]] std::filesystem::path model_path(const std::string &filename, const std::string &asset_path) {
116 if (!filename.empty()) {
117 return filename;
118 }
119 return std::filesystem::path(asset_path) / "data" / DEFAULT_MODEL;
120 }
121
122 [[nodiscard]] std::filesystem::path texture_path(const std::string &filename, const std::string &asset_path) {
123 const std::filesystem::path requested(filename);
124 if (requested.is_absolute() || std::filesystem::exists(requested)) {
125 return requested;
126 }
127
128 const std::filesystem::path from_asset_path = std::filesystem::path(asset_path) / requested;
129 if (std::filesystem::exists(from_asset_path)) {
130 return from_asset_path;
131 }
132 return requested;
133 }
134
135 [[nodiscard]] Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps) {
136 if (filename.empty()) {
137 return {};
138 }
139
140 const std::filesystem::path path = texture_path(filename, asset_path);
141 SurfacePtr loaded(mxvk::LoadPNG(path.string().c_str()));
142 if (!loaded) {
143 throw mxvk::Exception(std::format("{}: failed to load texture '{}'", APP_NAME, path.string()));
144 }
145
146 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
147 if (!rgba) {
148 throw mxvk::Exception(std::format("{}: failed to convert texture '{}': {}", APP_NAME, path.string(), SDL_GetError()));
149 }
150
151 const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(rgba->format);
152 if (format == nullptr) {
153 throw mxvk::Exception(std::format("{}: failed to query texture format '{}': {}", APP_NAME, path.string(), SDL_GetError()));
154 }
155
156 Texture texture;
157 MipLevel base_level;
158 base_level.width = rgba->w;
159 base_level.height = rgba->h;
160 base_level.pixels.resize(static_cast<std::size_t>(base_level.width) * static_cast<std::size_t>(base_level.height));
161 for (int y = 0; y < base_level.height; ++y) {
162 const auto *row = static_cast<const std::uint8_t *>(rgba->pixels) + static_cast<std::size_t>(y) * static_cast<std::size_t>(rgba->pitch);
163 const auto *source = reinterpret_cast<const std::uint32_t *>(row);
164 for (int x = 0; x < base_level.width; ++x) {
165 std::uint8_t red = 0;
166 std::uint8_t green = 0;
167 std::uint8_t blue = 0;
168 std::uint8_t alpha = 0;
169 SDL_GetRGBA(source[x], format, nullptr, &red, &green, &blue, &alpha);
170 base_level.pixels[static_cast<std::size_t>(y) * static_cast<std::size_t>(base_level.width) + static_cast<std::size_t>(x)] = pack_color(red, green, blue, alpha);
171 }
172 }
173 texture.levels.push_back(std::move(base_level));
174
175 while (generate_mipmaps && (texture.levels.back().width > 1 || texture.levels.back().height > 1)) {
176 const MipLevel &source = texture.levels.back();
177 MipLevel destination;
178 destination.width = std::max(1, (source.width + 1) / 2);
179 destination.height = std::max(1, (source.height + 1) / 2);
180 destination.pixels.resize(static_cast<std::size_t>(destination.width) * static_cast<std::size_t>(destination.height));
181 for (int y = 0; y < destination.height; ++y) {
182 for (int x = 0; x < destination.width; ++x) {
183 const int first_x = std::min(x * 2, source.width - 1);
184 const int second_x = std::min(first_x + 1, source.width - 1);
185 const int first_y = std::min(y * 2, source.height - 1);
186 const int second_y = std::min(first_y + 1, source.height - 1);
187 const auto texel = [&source](int source_x, int source_y) { return source.pixels[static_cast<std::size_t>(source_y) * static_cast<std::size_t>(source.width) + static_cast<std::size_t>(source_x)]; };
188 const std::array<mxvk::MXCOLOR, 4> colors = {
189 texel(first_x, first_y),
190 texel(second_x, first_y),
191 texel(first_x, second_y),
192 texel(second_x, second_y),
193 };
194 const auto average_channel = [&colors](auto component) {
195 std::uint32_t sum = 0;
196 for (const mxvk::MXCOLOR color : colors) {
197 sum += component(color);
198 }
199 return (sum + 2U) / 4U;
200 };
201 destination.pixels[static_cast<std::size_t>(y) * static_cast<std::size_t>(destination.width) + static_cast<std::size_t>(x)] = pack_color(average_channel(mxvk::color_r), average_channel(mxvk::color_g), average_channel(mxvk::color_b), average_channel(mxvk::color_a));
202 }
203 }
204 texture.levels.push_back(std::move(destination));
205 }
206
207 std::cout << std::format("{}: loaded texture '{}' ({}x{}, {} mip levels)\n", APP_NAME, path.string(), texture.width(), texture.height(), texture.levels.size());
208 return texture;
209 }
210
211 struct FaceDraw {
212 std::array<mxvk::vec4D, 3> points{};
213 std::array<mxvk::vec2D, 3> texcoords{};
216 bool repeat_horizontal = false;
217 float intensity = 1.0f;
218 };
219
220 [[nodiscard]] bool crosses_horizontal_texture_seam(const std::array<mxvk::vec2D, 3> &texcoords) {
221 const auto [minimum, maximum] = std::minmax_element(texcoords.begin(), texcoords.end(), [](const mxvk::vec2D &first, const mxvk::vec2D &second) { return first.x < second.x; });
222 const float span = maximum->x - minimum->x;
223 return span > 0.5f && span < 1.0f - mxvk::EPSILON;
224 }
225
226 [[nodiscard]] std::array<mxvk::vec2D, 3> unwrap_horizontal_texcoords(std::array<mxvk::vec2D, 3> texcoords) {
227 if (!crosses_horizontal_texture_seam(texcoords)) {
228 return texcoords;
229 }
230
231 for (mxvk::vec2D &texcoord : texcoords) {
232 if (texcoord.x < 0.5f) {
233 texcoord.x += 1.0f;
234 }
235 }
236
237 for (std::size_t index = 0; index < texcoords.size(); ++index) {
238 if (texcoords[index].y > mxvk::EPSILON && texcoords[index].y < 1.0f - mxvk::EPSILON) {
239 continue;
240 }
241
242 const std::size_t next = (index + 1) % texcoords.size();
243 const std::size_t previous = (index + texcoords.size() - 1) % texcoords.size();
244 texcoords[index].x = (texcoords[next].x + texcoords[previous].x) * 0.5f;
245 }
246 return texcoords;
247 }
248
249 constexpr float MIN_CAMERA_DISTANCE = 2.5f;
250 constexpr float MAX_CAMERA_DISTANCE = 12.0f;
251 constexpr float CAMERA_ZOOM_STEP = 0.45f;
252 constexpr float MOUSE_ROTATION_SENSITIVITY = 0.35f;
253 constexpr float MAX_PITCH_DEGREES = 89.0f;
254#if defined(MXVK_USE_EIGEN_MATH)
255 constexpr std::string_view BACKEND_NAME = "Eigen";
256#else
257 constexpr std::string_view BACKEND_NAME = "native";
258#endif
259} // namespace
260
261namespace example {
263 public:
264 Math3DModelLoaderWindow(const std::string &filename, const std::string &texture_filename, const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, bool repeat_texture, bool disable_warp_fix, bool disable_mipmap, float mip_bias, const FramebufferDimensions &framebuffer, bool benchmark, bool wireframe) : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync), override_texture(wireframe ? Texture{} : load_texture(texture_filename, asset_path, !disable_mipmap)), frame_width(framebuffer.width), frame_height(framebuffer.height), fallback_width(width), fallback_height(height), warp_fix_enabled(!disable_warp_fix), mipmapping_enabled(!disable_mipmap), mip_level_bias(mip_bias), texture_repeat_enabled(repeat_texture), benchmark_enabled(benchmark), wireframe_enabled(wireframe) {
265 setClearColor(0.012f, 0.015f, 0.022f, 1.0f);
267
268 const std::filesystem::path path = model_path(filename, asset_path);
269#if defined(MXVK_OBJ_LOADER)
270 const mxvk::vec4D scale(1.0f, 1.0f, 1.0f);
271#else
272 const mxvk::vec4D scale(MODEL_SCALE, MODEL_SCALE, MODEL_SCALE);
273#endif
274#if defined(MXVK_OBJ_LOADER)
275 const bool model_loaded = model.LoadOBJ(path.string(), scale, mxvk::vec4D(0.0f, 0.0f, 4.5f), mxvk::vec4D());
276#else
277 const bool model_loaded = model.LoadPLG(path.string(), scale, mxvk::vec4D(0.0f, 0.0f, 4.5f), mxvk::vec4D());
278#endif
279 if (!model_loaded) {
280 throw mxvk::Exception(std::format("{}: failed to load {} model '{}'", APP_NAME, MODEL_FORMAT, path.string()));
281 }
282#if defined(MXVK_OBJ_LOADER)
283 filter_auxiliary_objects();
284 fit_model_to_view();
285 if (!wireframe_enabled) {
286 load_material_textures(asset_path, !disable_mipmap);
287 }
288#endif
289#if defined(MXVK_USE_EIGEN_MATH)
290 local_vertex_batch.resize(4, static_cast<Eigen::Index>(model.local.size()));
291 camera_vertex_batch.resize(4, static_cast<Eigen::Index>(model.local.size()));
292 projected_vertex_batch.resize(4, static_cast<Eigen::Index>(model.local.size()));
293 inverse_vertex_depth.resize(static_cast<Eigen::Index>(model.local.size()));
294#if !defined(MXVK_OBJ_LOADER)
295 local_face_center_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
296 camera_face_center_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
297#endif
298 local_face_normal_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
299 camera_face_normal_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
300 triangle_intensity.resize(static_cast<Eigen::Index>(model.vlist.size()));
301#if !defined(MXVK_OBJ_LOADER)
302 triangle_visible.resize(static_cast<Eigen::Index>(model.vlist.size()));
303#endif
304 for (std::size_t index = 0; index < model.local.size(); ++index) {
305 local_vertex_batch.col(static_cast<Eigen::Index>(index)) = Eigen::Vector4f(model.local[index].x, model.local[index].y, model.local[index].z, model.local[index].w);
306 }
307#else
308 camera_vertices.resize(model.local.size());
309 projected_vertices.resize(model.local.size());
310#endif
311 initialize_face_geometry();
312 visible_faces.reserve(model.vlist.size());
313 std::cout << std::format("{}: loaded '{}' ({} vertices, {} triangles, {} materials)\n", APP_NAME, path.string(), model.num_vertices, model.num_polys, model.materials.size());
314 }
315
316 void event(SDL_Event &e) override {
317 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
318 exit();
319 }
320 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE && !e.key.repeat) {
321 automatic_rotation = !automatic_rotation;
322 return;
323 }
324 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
325 mouse_dragging = true;
326 last_mouse_x = e.button.x;
327 last_mouse_y = e.button.y;
328 return;
329 }
330 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
331 mouse_dragging = false;
332 return;
333 }
334 if (e.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
335 mouse_dragging = false;
336 return;
337 }
338 if (e.type == SDL_EVENT_MOUSE_MOTION && mouse_dragging) {
339 const float delta_x = e.motion.x - last_mouse_x;
340 const float delta_y = e.motion.y - last_mouse_y;
341 yaw_degrees = std::fmod(yaw_degrees + delta_x * MOUSE_ROTATION_SENSITIVITY, 360.0f);
342 pitch_degrees = std::clamp(pitch_degrees + delta_y * MOUSE_ROTATION_SENSITIVITY, -MAX_PITCH_DEGREES, MAX_PITCH_DEGREES);
343 last_mouse_x = e.motion.x;
344 last_mouse_y = e.motion.y;
345 return;
346 }
347 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
348 const float delta = e.wheel.y != 0.0f ? e.wheel.y : static_cast<float>(e.wheel.integer_y);
349 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
350 }
351 }
352
353 void proc() override {
354 const int output_width = swapchain_extent.width > 0U ? static_cast<int>(swapchain_extent.width) : fallback_width;
355 const int output_height = swapchain_extent.height > 0U ? static_cast<int>(swapchain_extent.height) : fallback_height;
356
357 ensure_framebuffer();
358 if (frame_sprite == nullptr || frame_surface == nullptr || frame_format == nullptr) {
359 return;
360 }
361
362 clear_frame(BACKGROUND_COLOR);
363 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
364
365 const std::uint64_t current_ticks = SDL_GetTicks();
366 const float elapsed_seconds = previous_frame_ticks == 0 ? 0.0f : static_cast<float>(current_ticks - previous_frame_ticks) * 0.001f;
367 previous_frame_ticks = current_ticks;
368 if (automatic_rotation && !mouse_dragging) {
369 yaw_degrees = std::fmod(yaw_degrees + elapsed_seconds * 42.0f, 360.0f);
370 }
371
372 if (benchmark_enabled && benchmark_frame_count == 0) {
373 benchmark_stopwatch = std::make_unique<StopWatch<HighResolutionClockPolicy>>(benchmark_name);
374 }
375
376 mxvk::Mat4D rotation;
377 rotation.BuildXYZ(pitch_degrees, yaw_degrees, 0.0f);
378 transform_and_project_vertices(rotation);
379
380 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
381 light_direction.Normalize();
382 visible_faces.clear();
383 build_visible_faces(rotation, light_direction);
384
385 for (const FaceDraw &face : visible_faces) {
386 if (wireframe_enabled) {
387 wireframe_pipeline.DrawWireframeTriangle(face.points[0], face.points[1], face.points[2], mxvk::shade_color(face.material_color, face.intensity), depth_buffer);
388 } else {
389 draw_gradient_triangle(face);
390 }
391 }
392
393 if (benchmark_stopwatch != nullptr) {
394 ++benchmark_frame_count;
395 if (benchmark_frame_count == BENCHMARK_FRAME_COUNT) {
396 benchmark_stopwatch->Stop();
397 benchmark_stopwatch.reset();
398 exit();
399 }
400 }
401
402 frame_sprite->updateTexture(frame_surface->pixels, frame_width, frame_height, frame_surface->pitch);
403 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
404 }
405
406 private:
408 mxvk::PipeLine wireframe_pipeline;
409 Texture override_texture;
410 std::vector<Texture> material_textures;
411 SurfacePtr frame_surface;
412#if defined(MXVK_USE_EIGEN_MATH)
413 using VertexBatch = Eigen::Matrix<float, 4, Eigen::Dynamic, Eigen::RowMajor>;
414 VertexBatch local_vertex_batch;
415 VertexBatch camera_vertex_batch;
416 VertexBatch projected_vertex_batch;
417 Eigen::RowVectorXf inverse_vertex_depth;
418 VertexBatch local_face_center_batch;
419 VertexBatch camera_face_center_batch;
420 VertexBatch local_face_normal_batch;
421 VertexBatch camera_face_normal_batch;
422 Eigen::RowVectorXf triangle_intensity;
423 Eigen::Array<bool, 1, Eigen::Dynamic> triangle_visible;
424#else
425 std::vector<mxvk::vec4D> camera_vertices;
426 std::vector<mxvk::vec4D> projected_vertices;
427 std::vector<mxvk::vec4D> local_face_centers;
428 std::vector<mxvk::vec4D> camera_face_centers;
429 std::vector<mxvk::vec4D> local_face_normals;
430 std::vector<mxvk::vec4D> camera_face_normals;
431#endif
432 std::vector<FaceDraw> visible_faces;
433 std::vector<float> depth_buffer;
434 const SDL_PixelFormatDetails *frame_format = nullptr;
435 mxvk::VK_Sprite *frame_sprite = nullptr;
436 int frame_width = 1280;
437 int frame_height = 720;
438 int fallback_width = 1280;
439 int fallback_height = 720;
440 float camera_distance = 4.25f;
441 float pitch_degrees = 0.0f;
442 float yaw_degrees = 0.0f;
443 float last_mouse_x = 0.0f;
444 float last_mouse_y = 0.0f;
445 bool mouse_dragging = false;
446 bool automatic_rotation = true;
447 std::uint64_t previous_frame_ticks = 0;
448 bool warp_fix_enabled = true;
449 bool mipmapping_enabled = true;
450 float mip_level_bias = 0.0f;
451 bool texture_repeat_enabled = false;
452 bool benchmark_enabled = false;
453 bool wireframe_enabled = false;
454 std::size_t benchmark_frame_count = 0;
455 std::string benchmark_name = std::format("{} geometry draw ({} backend, {} frames)", MODEL_FORMAT, BACKEND_NAME, BENCHMARK_FRAME_COUNT);
456 std::unique_ptr<StopWatch<HighResolutionClockPolicy>> benchmark_stopwatch;
457
458 static constexpr std::size_t BENCHMARK_FRAME_COUNT = 60 * 10;
459
460#if defined(MXVK_OBJ_LOADER)
461 void fit_model_to_view() {
462 if (model.vlist.empty()) {
463 throw mxvk::Exception(std::format("{}: cannot fit an OBJ model with no triangles", APP_NAME));
464 }
465
466 mxvk::vec4D minimum(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
467 mxvk::vec4D maximum(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
468 for (const mxvk::Triangle &triangle : model.vlist) {
469 for (const int vertex_index : triangle.vert) {
470 const mxvk::vec4D &vertex = model.local[static_cast<std::size_t>(vertex_index)];
471 minimum.x = std::min(minimum.x, vertex.x);
472 minimum.y = std::min(minimum.y, vertex.y);
473 minimum.z = std::min(minimum.z, vertex.z);
474 maximum.x = std::max(maximum.x, vertex.x);
475 maximum.y = std::max(maximum.y, vertex.y);
476 maximum.z = std::max(maximum.z, vertex.z);
477 }
478 }
479
480 const mxvk::vec4D center((minimum.x + maximum.x) * 0.5f, (minimum.y + maximum.y) * 0.5f, (minimum.z + maximum.z) * 0.5f);
481 float source_radius = 0.0f;
482 for (const mxvk::Triangle &triangle : model.vlist) {
483 for (const int vertex_index : triangle.vert) {
484 const mxvk::vec4D &vertex = model.local[static_cast<std::size_t>(vertex_index)];
485 source_radius = std::max(source_radius, std::sqrt((vertex.x - center.x) * (vertex.x - center.x) + (vertex.y - center.y) * (vertex.y - center.y) + (vertex.z - center.z) * (vertex.z - center.z)));
486 }
487 }
488 if (!std::isfinite(source_radius) || source_radius <= mxvk::EPSILON) {
489 throw mxvk::Exception(std::format("{}: cannot fit OBJ model with degenerate bounds", APP_NAME));
490 }
491
492 const float fit_scale = MODEL_FIT_RADIUS / source_radius;
493 for (mxvk::vec4D &vertex : model.local) {
494 vertex.x = (vertex.x - center.x) * fit_scale;
495 vertex.y = (vertex.y - center.y) * fit_scale;
496 vertex.z = (vertex.z - center.z) * fit_scale;
497 }
498 model.ComputeRad();
499 std::cout << std::format("{}: centered model and scaled radius {:.3f} to {:.3f} (scale {:.6f})\n", APP_NAME, source_radius, MODEL_FIT_RADIUS, fit_scale);
500 }
501#endif
502
503 void filter_auxiliary_objects() {
504 std::unordered_map<std::string, std::size_t> triangle_counts;
505 for (const mxvk::Triangle &triangle : model.vlist) {
506 ++triangle_counts[triangle.source_object_name];
507 }
508 if (triangle_counts.size() <= 1) {
509 return;
510 }
511
512 const auto dominant = std::max_element(triangle_counts.begin(), triangle_counts.end(), [](const auto &left, const auto &right) { return left.second < right.second; });
513 constexpr float DOMINANT_OBJECT_FRACTION = 0.95f;
514 if (static_cast<float>(dominant->second) < static_cast<float>(model.vlist.size()) * DOMINANT_OBJECT_FRACTION) {
515 return;
516 }
517
518 const std::size_t original_triangle_count = model.vlist.size();
519 std::erase_if(model.vlist, [&dominant](const mxvk::Triangle &triangle) { return triangle.source_object_name != dominant->first; });
520 model.num_polys = static_cast<int>(model.vlist.size());
521 model.object_name = dominant->first;
522 std::cout << std::format("{}: selected dominant OBJ object '{}' ({} triangles); ignored {} auxiliary triangle(s)\n", APP_NAME, dominant->first, dominant->second, original_triangle_count - model.vlist.size());
523 }
524
525 void load_material_textures(const std::string &asset_path, bool generate_mipmaps) {
526 if (!override_texture.empty()) {
527 return;
528 }
529
530 material_textures.resize(model.materials.size());
531 for (std::size_t index = 0; index < model.materials.size(); ++index) {
532 const std::string &texture_path = model.materials[index].diffuse_map;
533 if (!texture_path.empty()) {
534 material_textures[index] = load_texture(texture_path, asset_path, generate_mipmaps);
535 }
536 }
537 }
538
539 [[nodiscard]] const Texture *face_texture(int material_index) const {
540 if (!override_texture.empty()) {
541 return &override_texture;
542 }
543 if (material_index >= 0 && static_cast<std::size_t>(material_index) < material_textures.size() && !material_textures[static_cast<std::size_t>(material_index)].empty()) {
544 return &material_textures[static_cast<std::size_t>(material_index)];
545 }
546 return nullptr;
547 }
548
549#if defined(MXVK_USE_EIGEN_MATH)
550 static void transform_eigen_batch(const mxvk::Mat4D &matrix, const VertexBatch &input, VertexBatch &output) {
551 for (int component = 0; component < 4; ++component) {
552 output.row(component).array() = input.row(0).array() * matrix.mat[0][component] + input.row(1).array() * matrix.mat[1][component] + input.row(2).array() * matrix.mat[2][component] + input.row(3).array() * matrix.mat[3][component];
553 }
554 }
555#endif
556
557 void initialize_face_geometry() {
558#if !defined(MXVK_USE_EIGEN_MATH)
559 local_face_centers.resize(model.vlist.size());
560 camera_face_centers.resize(model.vlist.size());
561 local_face_normals.resize(model.vlist.size());
562 camera_face_normals.resize(model.vlist.size());
563#endif
564 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
565 const mxvk::Triangle &triangle = model.vlist[index];
566 const mxvk::vec4D &a = model.local[static_cast<std::size_t>(triangle.vert[0])];
567 const mxvk::vec4D &b = model.local[static_cast<std::size_t>(triangle.vert[1])];
568 const mxvk::vec4D &c = model.local[static_cast<std::size_t>(triangle.vert[2])];
569#if !defined(MXVK_USE_EIGEN_MATH) || !defined(MXVK_OBJ_LOADER)
570 const mxvk::vec4D center((a.x + b.x + c.x) * (1.0f / 3.0f), (a.y + b.y + c.y) * (1.0f / 3.0f), (a.z + b.z + c.z) * (1.0f / 3.0f), 1.0f);
571#endif
572 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
573 normal.Normalize();
574 normal.w = 0.0f;
575#if defined(MXVK_USE_EIGEN_MATH)
576 const Eigen::Index batch_index = static_cast<Eigen::Index>(index);
577#if !defined(MXVK_OBJ_LOADER)
578 local_face_center_batch.col(batch_index) = Eigen::Vector4f(center.x, center.y, center.z, center.w);
579#endif
580 local_face_normal_batch.col(batch_index) = Eigen::Vector4f(normal.x, normal.y, normal.z, normal.w);
581#else
582 local_face_centers[index] = center;
583 local_face_normals[index] = normal;
584#endif
585 }
586 }
587
588 void transform_and_project_vertices(const mxvk::Mat4D &rotation) {
589#if defined(MXVK_USE_EIGEN_MATH)
590 transform_eigen_batch(rotation, local_vertex_batch, camera_vertex_batch);
591 camera_vertex_batch.row(2).array() += camera_distance;
592
593 const float scale = static_cast<float>(std::min(frame_width, frame_height)) * 0.52f;
594 const float center_x = static_cast<float>(frame_width) * 0.5f;
595 const float center_y = static_cast<float>(frame_height) * 0.5f;
596 inverse_vertex_depth.array() = camera_vertex_batch.row(2).array().max(0.001f).inverse();
597 projected_vertex_batch.row(0).array() = center_x + camera_vertex_batch.row(0).array() * inverse_vertex_depth.array() * scale;
598 projected_vertex_batch.row(1).array() = center_y - camera_vertex_batch.row(1).array() * inverse_vertex_depth.array() * scale;
599 projected_vertex_batch.row(2) = camera_vertex_batch.row(2);
600 projected_vertex_batch.row(3).setOnes();
601#else
602 rotation.MulVec(model.local, camera_vertices);
603 for (std::size_t index = 0; index < model.local.size(); ++index) {
604 camera_vertices[index].z += camera_distance;
605 projected_vertices[index] = project_to_screen(camera_vertices[index], frame_width, frame_height);
606 }
607#endif
608 }
609
610 [[nodiscard]] mxvk::vec4D projected_vertex(std::size_t index) const {
611#if defined(MXVK_USE_EIGEN_MATH)
612 const Eigen::Index batch_index = static_cast<Eigen::Index>(index);
613 return {
614 projected_vertex_batch(0, batch_index),
615 projected_vertex_batch(1, batch_index),
616 projected_vertex_batch(2, batch_index),
617 projected_vertex_batch(3, batch_index),
618 };
619#else
620 return projected_vertices[index];
621#endif
622 }
623
624 void append_visible_face(const mxvk::Triangle &triangle, float intensity) {
625 const auto first = static_cast<std::size_t>(triangle.vert[0]);
626 const auto second = static_cast<std::size_t>(triangle.vert[1]);
627 const auto third = static_cast<std::size_t>(triangle.vert[2]);
628 std::array<mxvk::vec2D, 3> texcoords = {
629 model.texcoords[first],
630 model.texcoords[second],
631 model.texcoords[third],
632 };
633#if defined(MXVK_OBJ_LOADER)
634 for (mxvk::vec2D &texcoord : texcoords) {
635 texcoord.y = 1.0f - texcoord.y;
636 }
637#endif
638 const bool repeat_horizontal = face_texture(triangle.material_index) != nullptr && texture_repeat_enabled;
639 if (repeat_horizontal) {
640 texcoords = unwrap_horizontal_texcoords(texcoords);
641 }
642 visible_faces.push_back({
643 {projected_vertex(first), projected_vertex(second), projected_vertex(third)},
644 texcoords,
645 triangle.color,
646 triangle.material_index,
647 repeat_horizontal,
648 intensity,
649 });
650 }
651
652 void build_visible_faces(const mxvk::Mat4D &rotation_matrix, const mxvk::vec4D &light_direction) {
653#if defined(MXVK_USE_EIGEN_MATH)
654#if !defined(MXVK_OBJ_LOADER)
655 transform_eigen_batch(rotation_matrix, local_face_center_batch, camera_face_center_batch);
656 camera_face_center_batch.row(2).array() += camera_distance;
657#endif
658 transform_eigen_batch(rotation_matrix, local_face_normal_batch, camera_face_normal_batch);
659#if !defined(MXVK_OBJ_LOADER)
660 triangle_visible = (-camera_face_normal_batch.row(0).array() * camera_face_center_batch.row(0).array() - camera_face_normal_batch.row(1).array() * camera_face_center_batch.row(1).array() - camera_face_normal_batch.row(2).array() * camera_face_center_batch.row(2).array()) > 0.0f;
661#endif
662
663 const auto diffuse = (camera_face_normal_batch.row(0).array() * light_direction.x + camera_face_normal_batch.row(1).array() * light_direction.y + camera_face_normal_batch.row(2).array() * light_direction.z).max(0.0f);
664 triangle_intensity.array() = (0.35f + diffuse * 0.65f).min(1.0f);
665
666 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
667 const Eigen::Index batch_index = static_cast<Eigen::Index>(index);
668#if !defined(MXVK_OBJ_LOADER)
669 if (!triangle_visible(batch_index)) {
670 continue;
671 }
672#endif
673 const mxvk::Triangle &triangle = model.vlist[index];
674 append_visible_face(triangle, triangle_intensity(batch_index));
675 }
676#else
677 rotation_matrix.MulVec(local_face_centers, camera_face_centers);
678 rotation_matrix.MulVec(local_face_normals, camera_face_normals);
679 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
680 const mxvk::Triangle &triangle = model.vlist[index];
681 mxvk::vec4D &center = camera_face_centers[index];
682 center.z += camera_distance;
683 const mxvk::vec4D &normal = camera_face_normals[index];
684 const mxvk::vec4D view_direction(-center.x, -center.y, -center.z, 0.0f);
685#if !defined(MXVK_OBJ_LOADER)
686 if (normal.DotProduct(view_direction) <= 0.0f) {
687 continue;
688 }
689#endif
690
691 const float diffuse = std::max(0.0f, normal.DotProduct(light_direction));
692 append_visible_face(triangle, std::clamp(0.35f + diffuse * 0.65f, 0.0f, 1.0f));
693 }
694#endif
695 }
696
697 void ensure_framebuffer() {
698 if (frame_surface != nullptr) {
699 return;
700 }
701
702 frame_surface = create_frame_surface(frame_width, frame_height);
703 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
704 if (frame_format == nullptr) {
705 throw mxvk::Exception(std::format("{}: failed to query frame format: {}", APP_NAME, SDL_GetError()));
706 }
707
708 depth_buffer.resize(static_cast<std::size_t>(frame_width) * static_cast<std::size_t>(frame_height));
709 clear_frame(BACKGROUND_COLOR);
710 wireframe_pipeline.Begin(frame_width, frame_height, [this](int x, int y, mxvk::MXCOLOR color) { put_pixel(x, y, color); });
711
712 frame_sprite = createSprite(frame_surface.get());
713 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
714 }
715
716 [[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)); }
717
718 void clear_frame(mxvk::MXCOLOR color) { SDL_FillSurfaceRect(frame_surface.get(), nullptr, map_color(color)); }
719
720 void put_pixel(int x, int y, mxvk::MXCOLOR color) {
721 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
722 return;
723 }
724
725 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + (static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_surface->pitch));
726 auto *pixel = reinterpret_cast<std::uint32_t *>(row) + x;
727 *pixel = map_color(color);
728 }
729
730 [[nodiscard]] float texture_level_for_face(const FaceDraw &face, const Texture *texture, float area) const {
731 if (texture == nullptr || !mipmapping_enabled) {
732 return 0.0f;
733 }
734
735 const mxvk::vec4D &a = face.points[0];
736 const mxvk::vec4D &b = face.points[1];
737 const mxvk::vec4D &c = face.points[2];
738 const float weight_a_dx = (c.y - b.y) / area;
739 const float weight_a_dy = -(c.x - b.x) / area;
740 const float weight_b_dx = (a.y - c.y) / area;
741 const float weight_b_dy = -(a.x - c.x) / area;
742 const float weight_c_dx = (b.y - a.y) / area;
743 const float weight_c_dy = -(b.x - a.x) / area;
744
745 float u_dx = 0.0f;
746 float u_dy = 0.0f;
747 float v_dx = 0.0f;
748 float v_dy = 0.0f;
749 if (warp_fix_enabled) {
750 const float inverse_z_a = 1.0f / a.z;
751 const float inverse_z_b = 1.0f / b.z;
752 const float inverse_z_c = 1.0f / c.z;
753 const float inverse_z = (inverse_z_a + inverse_z_b + inverse_z_c) / 3.0f;
754 const float inverse_z_dx = weight_a_dx * inverse_z_a + weight_b_dx * inverse_z_b + weight_c_dx * inverse_z_c;
755 const float inverse_z_dy = weight_a_dy * inverse_z_a + weight_b_dy * inverse_z_b + weight_c_dy * inverse_z_c;
756 const auto corrected_derivatives = [&](float first, float second, float third) {
757 const float value_over_z = (first * inverse_z_a + second * inverse_z_b + third * inverse_z_c) / 3.0f;
758 const float value_over_z_dx = weight_a_dx * first * inverse_z_a + weight_b_dx * second * inverse_z_b + weight_c_dx * third * inverse_z_c;
759 const float value_over_z_dy = weight_a_dy * first * inverse_z_a + weight_b_dy * second * inverse_z_b + weight_c_dy * third * inverse_z_c;
760 const float denominator = inverse_z * inverse_z;
761 return std::array<float, 2>{
762 (value_over_z_dx * inverse_z - value_over_z * inverse_z_dx) / denominator,
763 (value_over_z_dy * inverse_z - value_over_z * inverse_z_dy) / denominator,
764 };
765 };
766 const std::array<float, 2> u_derivatives = corrected_derivatives(face.texcoords[0].x, face.texcoords[1].x, face.texcoords[2].x);
767 const std::array<float, 2> v_derivatives = corrected_derivatives(face.texcoords[0].y, face.texcoords[1].y, face.texcoords[2].y);
768 u_dx = u_derivatives[0];
769 u_dy = u_derivatives[1];
770 v_dx = v_derivatives[0];
771 v_dy = v_derivatives[1];
772 } else {
773 u_dx = weight_a_dx * face.texcoords[0].x + weight_b_dx * face.texcoords[1].x + weight_c_dx * face.texcoords[2].x;
774 u_dy = weight_a_dy * face.texcoords[0].x + weight_b_dy * face.texcoords[1].x + weight_c_dy * face.texcoords[2].x;
775 v_dx = weight_a_dx * face.texcoords[0].y + weight_b_dx * face.texcoords[1].y + weight_c_dx * face.texcoords[2].y;
776 v_dy = weight_a_dy * face.texcoords[0].y + weight_b_dy * face.texcoords[1].y + weight_c_dy * face.texcoords[2].y;
777 }
778
779 const float horizontal_footprint = std::hypot(u_dx * static_cast<float>(texture->width()), v_dx * static_cast<float>(texture->height()));
780 const float vertical_footprint = std::hypot(u_dy * static_cast<float>(texture->width()), v_dy * static_cast<float>(texture->height()));
781 return std::log2(std::max({mxvk::EPSILON, horizontal_footprint, vertical_footprint})) + mip_level_bias;
782 }
783
784 void draw_gradient_triangle(const FaceDraw &face) {
785 const mxvk::vec4D &a = face.points[0];
786 const mxvk::vec4D &b = face.points[1];
787 const mxvk::vec4D &c = face.points[2];
788 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); };
789
790 const float area = edge(b, c, a.x, a.y);
791 if (std::abs(area) <= mxvk::EPSILON) {
792 return;
793 }
794
795 const int min_x = std::clamp(static_cast<int>(std::floor(std::min({a.x, b.x, c.x}))), 0, frame_width - 1);
796 const int max_x = std::clamp(static_cast<int>(std::ceil(std::max({a.x, b.x, c.x}))), 0, frame_width - 1);
797 const int min_y = std::clamp(static_cast<int>(std::floor(std::min({a.y, b.y, c.y}))), 0, frame_height - 1);
798 const int max_y = std::clamp(static_cast<int>(std::ceil(std::max({a.y, b.y, c.y}))), 0, frame_height - 1);
799 const Texture *texture = face_texture(face.material_index);
800 const float texture_level = texture_level_for_face(face, texture, area);
801
802 for (int y = min_y; y <= max_y; ++y) {
803 for (int x = min_x; x <= max_x; ++x) {
804 const float sample_x = static_cast<float>(x) + 0.5f;
805 const float sample_y = static_cast<float>(y) + 0.5f;
806 const float weight_a = edge(b, c, sample_x, sample_y) / area;
807 const float weight_b = edge(c, a, sample_x, sample_y) / area;
808 const float weight_c = edge(a, b, sample_x, sample_y) / area;
809 if (weight_a < -mxvk::EPSILON || weight_b < -mxvk::EPSILON || weight_c < -mxvk::EPSILON) {
810 continue;
811 }
812
813 const float reciprocal_depth = weight_a / a.z + weight_b / b.z + weight_c / c.z;
814 if (reciprocal_depth <= mxvk::EPSILON) {
815 continue;
816 }
817
818 const float depth = 1.0f / reciprocal_depth;
819 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);
820 if (depth >= depth_buffer[pixel_index]) {
821 continue;
822 }
823 depth_buffer[pixel_index] = depth;
824
825 const float texture_weight_a = warp_fix_enabled ? (weight_a / a.z) * depth : weight_a;
826 const float texture_weight_b = warp_fix_enabled ? (weight_b / b.z) * depth : weight_b;
827 const float texture_weight_c = warp_fix_enabled ? (weight_c / c.z) * depth : weight_c;
828 const float u = face.texcoords[0].x * texture_weight_a + face.texcoords[1].x * texture_weight_b + face.texcoords[2].x * texture_weight_c;
829 const float v = face.texcoords[0].y * texture_weight_a + face.texcoords[1].y * texture_weight_b + face.texcoords[2].y * texture_weight_c;
830 mxvk::MXCOLOR color = face.material_color;
831 if (texture != nullptr) {
832 color = texture->sample(u, v, texture_level, face.repeat_horizontal);
833 }
834#if !defined(MXVK_OBJ_LOADER)
835 else {
836 color = gradient_color(u, v);
837 }
838#endif
839 put_pixel(x, y, mxvk::shade_color(color, face.intensity));
840 }
841 }
842 }
843
844 [[nodiscard]] static mxvk::MXCOLOR gradient_color(float u, float v) {
845 u = std::clamp(u, 0.0f, 1.0f);
846 v = std::clamp(v, 0.0f, 1.0f);
847
848 constexpr mxvk::MXCOLOR BOTTOM_LEFT = mxvk::MXVK_RGB(68, 214, 255);
849 constexpr mxvk::MXCOLOR BOTTOM_RIGHT = mxvk::MXVK_RGB(92, 255, 142);
850 constexpr mxvk::MXCOLOR TOP_LEFT = mxvk::MXVK_RGB(155, 105, 255);
851 constexpr mxvk::MXCOLOR TOP_RIGHT = mxvk::MXVK_RGB(255, 82, 197);
852 const auto bilinear_channel = [&](auto component) {
853 const float bottom = static_cast<float>(component(BOTTOM_LEFT)) + (static_cast<float>(component(BOTTOM_RIGHT)) - static_cast<float>(component(BOTTOM_LEFT))) * u;
854 const float top = static_cast<float>(component(TOP_LEFT)) + (static_cast<float>(component(TOP_RIGHT)) - static_cast<float>(component(TOP_LEFT))) * u;
855 return std::clamp(static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
856 };
857
858 return mxvk::MXVK_RGB(bilinear_channel(mxvk::color_r), bilinear_channel(mxvk::color_g), bilinear_channel(mxvk::color_b));
859 }
860
861 [[nodiscard]] static mxvk::vec4D project_to_screen(const mxvk::vec4D &point, int width, int height) {
862 const float scale = static_cast<float>(std::min(width, height)) * 0.52f;
863 const float center_x = static_cast<float>(width) * 0.5f;
864 const float center_y = static_cast<float>(height) * 0.5f;
865 const float z = std::max(point.z, 0.001f);
866 return {center_x + (point.x / z) * scale, center_y - (point.y / z) * scale, point.z, 1.0f};
867 }
868 };
869} // namespace example
870
871int main(int argc, char **argv) {
872 try {
873 Arguments args = proc_args(argc, argv);
874 FramebufferDimensions framebuffer = args.framebuffer;
875 if (args.benchmark && !args.framebufferSpecified) {
876 framebuffer = {320, 180};
877 std::cout << APP_NAME << ": benchmark framebuffer defaults to 320x180; use --framebuffer to override\n";
878 }
879 example::Math3DModelLoaderWindow window(args.filename, args.texture, args.path, std::string(WINDOW_TITLE), args.width, args.height, args.fullscreen, args.enable_vsync, args.repeat, args.nowarpfix, args.disable_mipmap, args.mip_bias, framebuffer, args.benchmark, args.wireframe);
880 window.loop();
881 } catch (mxvk::Exception &e) {
882 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
883 return EXIT_FAILURE;
884 } catch (ArgException<std::string> &e) {
885 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
886 return EXIT_FAILURE;
887 }
888 return EXIT_SUCCESS;
889}
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 operator()(SDL_Surface *surface) const
Definition main.cpp:48
Math3DModelLoaderWindow(const std::string &filename, const std::string &texture_filename, const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, bool repeat_texture, bool disable_warp_fix, bool disable_mipmap, float mip_bias, const FramebufferDimensions &framebuffer, bool benchmark, bool wireframe)
Definition main.cpp:264
void proc() override
Execute one processing/update step.
Definition main.cpp:353
void event(SDL_Event &e) override
Handle one SDL event.
Definition main.cpp:316
std::string text() const
Four-by-four homogeneous transform matrix.
Definition mxvk_math.h:706
float mat[4][4]
Matrix elements indexed as row, column.
Definition mxvk_math.h:709
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
Clipped software rasterization pipeline for lines and filled triangles.
Definition mxvk_math.h:2104
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.
Simple mesh object loaded from PLG-style indexed triangle data.
Definition mxvk_math.h:1300
Two-dimensional float vector with common arithmetic helpers.
Definition mxvk_math.h:158
Four-dimensional float vector used for homogeneous 3D coordinates.
Definition mxvk_math.h:363
float y
Y coordinate.
Definition mxvk_math.h:369
float x
X coordinate.
Definition mxvk_math.h:366
float w
Homogeneous W coordinate.
Definition mxvk_math.h:375
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
#define MXVK_VALIDATION
Definition mxvk.hpp:28
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
PNG image loading and saving utilities via SDL3.
mxvk::MXCOLOR pack_color(std::uint32_t red, std::uint32_t green, std::uint32_t blue, std::uint32_t alpha)
Definition main.cpp:53
constexpr float MODEL_SCALE
Definition main.cpp:42
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
Definition main.cpp:27
std::array< mxvk::vec2D, 3 > unwrap_horizontal_texcoords(std::array< mxvk::vec2D, 3 > texcoords)
Definition main.cpp:226
std::filesystem::path texture_path(const std::string &filename, const std::string &asset_path)
Definition main.cpp:122
Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps)
Definition main.cpp:135
constexpr float MAX_PITCH_DEGREES
Definition main.cpp:253
constexpr float MIN_CAMERA_DISTANCE
Definition main.cpp:249
constexpr std::string_view MODEL_FORMAT
Definition main.cpp:39
constexpr float MAX_CAMERA_DISTANCE
Definition main.cpp:250
mxvk::MXCOLOR interpolate_color(mxvk::MXCOLOR first, mxvk::MXCOLOR second, float fraction)
Definition main.cpp:55
constexpr std::string_view WINDOW_TITLE
Definition main.cpp:41
bool crosses_horizontal_texture_seam(const std::array< mxvk::vec2D, 3 > &texcoords)
Definition main.cpp:220
SurfacePtr create_frame_surface(int width, int height)
Definition main.cpp:29
constexpr std::string_view DEFAULT_MODEL
Definition main.cpp:40
constexpr float MOUSE_ROTATION_SENSITIVITY
Definition main.cpp:252
constexpr float CAMERA_ZOOM_STEP
Definition main.cpp:251
constexpr mxvk::MXCOLOR BACKGROUND_COLOR
Definition main.cpp:43
constexpr std::string_view APP_NAME
Definition main.cpp:38
std::filesystem::path model_path(const std::string &filename, const std::string &asset_path)
Definition main.cpp:115
constexpr std::string_view BACKEND_NAME
Definition main.cpp:257
int main()
Definition main.py:165
Definition model.py:1
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
Definition mxvk_math.h:52
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
Definition mxvk_math.h:40
void BuildTables()
Rebuild the sine and cosine lookup tables.
Definition mxvk_math.h:98
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:89
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
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
float mip_bias
Mipmap level-of-detail bias requested by --mip-bias.
Definition argz.hpp:745
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:738
bool nowarpfix
Disable perspective-correct texture mapping (--nowarpfix).
Definition argz.hpp:743
std::string texture
Optional texture file path (--texture).
Definition argz.hpp:752
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:721
bool repeat
Enable repeat behavior such as playback looping or wrapped textures.
Definition argz.hpp:735
std::string filename
Optional input filename (--filename).
Definition argz.hpp:726
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:723
bool benchmark
Enable application benchmark mode (--benchmark).
Definition argz.hpp:741
bool wireframe
Render supported 3D models as wireframes (--wireframe).
Definition argz.hpp:742
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:720
bool disable_mipmap
Disable mipmap generation and selection (--disable-mipmap).
Definition argz.hpp:744
Parsed software framebuffer dimensions.
Definition argz.hpp:709
std::array< mxvk::vec4D, 3 > points
Definition main.cpp:212
std::array< mxvk::vec2D, 3 > texcoords
Definition main.cpp:213
std::vector< mxvk::MXCOLOR > pixels
Definition main.cpp:65
mxvk::MXCOLOR sample_bilinear(float u, float v, bool repeat_horizontal) const
Definition main.cpp:67
mxvk::MXCOLOR sample(float u, float v, float level, bool repeat_horizontal) const
Definition main.cpp:94
std::vector< MipLevel > levels
Definition main.cpp:86
Triangle primitive used by the simple software rendering pipeline.
Definition mxvk_math.h:1187
std::string source_object_name
Wavefront object name from the o section containing this triangle.
Definition mxvk_math.h:1213
int material_index
Index of the OBJ/MTL material used by this triangle, or -1.
Definition mxvk_math.h:1207
MXCOLOR color
Triangle color.
Definition mxvk_math.h:1195
vec4D vlist[3]
Working vertex positions.
Definition mxvk_math.h:1189
int vert[3]
Indices into an object's vertex arrays.
Definition mxvk_math.h:1204