4#if defined(MXVK_USE_EIGEN_MATH)
25 class SurfaceDeleter {
30 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
32 SurfacePtr create_frame_surface(
int width,
int height) {
33 SurfacePtr
surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
35 throw mxvk::Exception(std::format(
"Failed to create 3dmath_texture_array frame surface: {}", SDL_GetError()));
43 std::vector<mxvk::MXCOLOR>
pixels;
50 u = std::clamp(u, 0.0f, 1.0f);
51 v = std::clamp(v, 0.0f, 1.0f);
52 const int x = std::clamp(
static_cast<int>(u *
static_cast<float>(
width - 1) + 0.5f), 0,
width - 1);
53 const int y = std::clamp(
static_cast<int>(v *
static_cast<float>(
height - 1) + 0.5f), 0,
height - 1);
54 return pixels[
static_cast<std::size_t
>(y *
width + x)];
58 u = std::clamp(u, 0.0f, 1.0f);
59 v = std::clamp(v, 0.0f, 1.0f);
60 const int x =
static_cast<int>(u *
static_cast<float>(
width - 1) + 0.5f);
61 const int y =
static_cast<int>(v *
static_cast<float>(
height - 1) + 0.5f);
62 return pixels[
static_cast<std::size_t
>(y *
width + x)];
75 float intensity = 1.0f;
78 [[nodiscard]] std::string resolve_texture_path(
const Arguments &args) {
80 if (texture_path.empty()) {
81 throw mxvk::Exception(
"3dmath_texture_array: pass a PNG with --filename <file.png> or --texture <file.png>");
84 namespace fs = std::filesystem;
85 fs::path requested(texture_path);
86 if (requested.is_absolute() || fs::exists(requested)) {
87 return requested.string();
90 if (!args.
path.empty()) {
91 const fs::path from_asset_path = fs::path(args.
path) / requested;
92 if (fs::exists(from_asset_path)) {
93 return from_asset_path.string();
97 return requested.string();
100 [[nodiscard]] Texture
load_texture(
const std::string &path) {
103 throw mxvk::Exception(std::format(
"3dmath_texture_array: failed to load PNG '{}'", path));
106 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
108 throw mxvk::Exception(std::format(
"3dmath_texture_array: failed to convert PNG '{}': {}", path, SDL_GetError()));
111 const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(rgba->format);
112 if (format ==
nullptr) {
113 throw mxvk::Exception(std::format(
"3dmath_texture_array: failed to query PNG format '{}': {}", path, SDL_GetError()));
117 texture.width = rgba->w;
118 texture.height = rgba->h;
119 texture.pixels.resize(
static_cast<std::size_t
>(texture.width * texture.height));
121 for (
int y = 0; y < texture.height; ++y) {
122 const auto *row =
static_cast<const std::uint8_t *
>(rgba->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(rgba->pitch));
123 const auto *src =
reinterpret_cast<const std::uint32_t *
>(row);
124 for (
int x = 0; x < texture.width; ++x) {
129 SDL_GetRGBA(src[x], format,
nullptr, &r, &g, &b, &a);
141 Math3DTextureArrayWindow(
const Arguments &args,
const std::string &title) :
mxvk::
VK_Window(title, args.width, args.height, args.fullscreen,
MXVK_VALIDATION, args.enable_vsync), texture(load_texture(resolve_texture_path(args))), frame_width(args.framebuffer.width), frame_height(args.framebuffer.height), fallback_width(args.width), fallback_height(args.height) {
147 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
150 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
151 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
152 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
160 ensure_framebuffer();
161 if (frame_sprite ==
nullptr || frame_surface ==
nullptr || frame_format ==
nullptr) {
165 const float time =
static_cast<float>(SDL_GetTicks()) * 0.001f;
168 const std::array<mxvk::vec4D, 8> cube_vertices = {
169 mxvk::vec4D{-CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
170 mxvk::vec4D{CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
171 mxvk::vec4D{CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
172 mxvk::vec4D{-CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
173 mxvk::vec4D{-CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
174 mxvk::vec4D{CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
175 mxvk::vec4D{CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
176 mxvk::vec4D{-CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
180 rotation.
BuildXYZ(time * 31.0f, time * 43.0f, time * 17.0f);
182 const std::array<std::array<int, 4>, 6> cube_faces = {{
194 std::vector<FaceDraw> faces;
195 faces.reserve(GRID_CUBE_COUNT * cube_faces.size());
197 for (
int grid_z = -GRID_RADIUS; grid_z <= GRID_RADIUS; ++grid_z) {
198 for (
int grid_y = -GRID_RADIUS; grid_y <= GRID_RADIUS; ++grid_y) {
199 for (
int grid_x = -GRID_RADIUS; grid_x <= GRID_RADIUS; ++grid_x) {
200 const mxvk::vec4D cube_center(
static_cast<float>(grid_x) * GRID_SPACING,
static_cast<float>(grid_y) * GRID_SPACING,
static_cast<float>(grid_z) * GRID_SPACING, 0.0f);
202 std::array<mxvk::vec4D, 8> camera_vertices{};
203 std::array<mxvk::vec4D, 8> projected{};
204 for (std::size_t i = 0; i < cube_vertices.size(); ++i) {
206 point.
z += camera_distance;
207 camera_vertices[i] = point;
208 projected[i] = project_to_screen(point, frame_width, frame_height);
211 for (
const auto &indices : cube_faces) {
212 const auto index0 =
static_cast<std::size_t
>(indices[0]);
213 const auto index1 =
static_cast<std::size_t
>(indices[1]);
214 const auto index2 =
static_cast<std::size_t
>(indices[2]);
215 const auto index3 =
static_cast<std::size_t
>(indices[3]);
222 const mxvk::vec4D center = (a + b + c + camera_vertices[index3]) * 0.25f;
223 const mxvk::vec4D view_vector(-center.
x, -center.
y, -center.
z, 1.0f);
231 {projected[index0], {0.0f, 1.0f}, camera_vertices[index0].z},
232 {projected[index1], {1.0f, 1.0f}, camera_vertices[index1].z},
233 {projected[index2], {1.0f, 0.0f}, camera_vertices[index2].z},
234 {projected[index3], {0.0f, 0.0f}, camera_vertices[index3].z},
236 face.depth = center.
z;
237 face.intensity = std::clamp(0.35f + diffuse * 0.65f, 0.0f, 1.0f);
238 faces.push_back(face);
244 std::ranges::sort(faces, [](
const FaceDraw &left,
const FaceDraw &right) {
return left.depth > right.depth; });
246 for (
const FaceDraw &face : faces) {
247 draw_textured_triangle(face.vertices[0], face.vertices[1], face.vertices[2], face.intensity);
248 draw_textured_triangle(face.vertices[0], face.vertices[2], face.vertices[3], face.intensity);
251 frame_sprite->updateTexture(frame_surface->pixels, frame_width, frame_height, frame_surface->pitch);
252 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
257 SurfacePtr frame_surface;
258 const SDL_PixelFormatDetails *frame_format =
nullptr;
260 int frame_width = 1280;
261 int frame_height = 720;
262 int fallback_width = 1280;
263 int fallback_height = 720;
264 float camera_distance = 8.5f;
265 static constexpr int GRID_RADIUS = 1;
266 static constexpr std::size_t
GRID_WIDTH =
static_cast<std::size_t
>((GRID_RADIUS * 2) + 1);
268 static constexpr float CUBE_HALF_EXTENT = 0.52f;
269 static constexpr float GRID_SPACING = 1.45f;
270 static constexpr float MIN_CAMERA_DISTANCE = 5.0f;
271 static constexpr float MAX_CAMERA_DISTANCE = 18.0f;
272 static constexpr float CAMERA_ZOOM_STEP = 0.65f;
274 void ensure_framebuffer() {
275 if (frame_surface !=
nullptr) {
279 frame_surface = create_frame_surface(frame_width, frame_height);
280 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
281 if (frame_format ==
nullptr) {
282 throw mxvk::Exception(std::format(
"Failed to query 3dmath_texture_array frame format: {}", SDL_GetError()));
287 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
292 void clear_frame(
mxvk::MXCOLOR color) { SDL_FillSurfaceRect(frame_surface.get(),
nullptr, map_color(color)); }
294 void put_shaded_pixel_unchecked(
int x,
int y,
mxvk::MXCOLOR color, std::uint16_t intensity) {
295 auto *row =
static_cast<std::uint8_t *
>(frame_surface->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_surface->pitch));
296 auto *pixel = row + (
static_cast<std::size_t
>(x) * 4U);
297 pixel[0] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_r(color)) * intensity) >> 8U);
298 pixel[1] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_g(color)) * intensity) >> 8U);
299 pixel[2] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_b(color)) * intensity) >> 8U);
303 void draw_textured_triangle(
const TexVertex &a,
const TexVertex &b,
const TexVertex &c,
float intensity) {
304 if (texture.width <= 0 || texture.height <= 0 || texture.pixels.empty()) {
308 const mxvk::vec2D p0(a.position.x, a.position.y);
309 const mxvk::vec2D p1(b.position.x, b.position.y);
310 const mxvk::vec2D p2(c.position.x, c.position.y);
315 const bool positive_area = area > 0.0f;
317 const int min_x = std::max(0,
static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x}))));
318 const int max_x = std::min(frame_width - 1,
static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x}))));
319 const int min_y = std::max(0,
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y}))));
320 const int max_y = std::min(frame_height - 1,
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y}))));
322 if (min_x > max_x || min_y > max_y) {
326 const float inv_area = 1.0f / area;
327 const float inv_z0 = 1.0f / std::max(a.depth, 0.001f);
328 const float inv_z1 = 1.0f / std::max(b.depth, 0.001f);
329 const float inv_z2 = 1.0f / std::max(c.depth, 0.001f);
330 const float u_over_z0 = a.uv.x * inv_z0;
331 const float u_over_z1 = b.uv.x * inv_z1;
332 const float u_over_z2 = c.uv.x * inv_z2;
333 const float v_over_z0 = a.uv.y * inv_z0;
334 const float v_over_z1 = b.uv.y * inv_z1;
335 const float v_over_z2 = c.uv.y * inv_z2;
336 const std::uint16_t fixed_intensity =
static_cast<std::uint16_t
>(std::clamp(intensity, 0.0f, 1.0f) * 256.0f);
338 const float w0_dx = p2.y - p1.y;
339 const float w0_dy = -(p2.x - p1.x);
340 const float w1_dx = p0.y - p2.y;
341 const float w1_dy = -(p0.x - p2.x);
342 const float w2_dx = p1.y - p0.y;
343 const float w2_dy = -(p1.x - p0.x);
345 const mxvk::vec2D row_start(
static_cast<float>(min_x) + 0.5f,
static_cast<float>(min_y) + 0.5f);
349 float row_inv_z = ((row_w0 * inv_z0) + (row_w1 * inv_z1) + (row_w2 * inv_z2)) * inv_area;
350 float row_u_over_z = ((row_w0 * u_over_z0) + (row_w1 * u_over_z1) + (row_w2 * u_over_z2)) * inv_area;
351 float row_v_over_z = ((row_w0 * v_over_z0) + (row_w1 * v_over_z1) + (row_w2 * v_over_z2)) * inv_area;
353 const float inv_z_dx = ((w0_dx * inv_z0) + (w1_dx * inv_z1) + (w2_dx * inv_z2)) * inv_area;
354 const float inv_z_dy = ((w0_dy * inv_z0) + (w1_dy * inv_z1) + (w2_dy * inv_z2)) * inv_area;
355 const float u_over_z_dx = ((w0_dx * u_over_z0) + (w1_dx * u_over_z1) + (w2_dx * u_over_z2)) * inv_area;
356 const float u_over_z_dy = ((w0_dy * u_over_z0) + (w1_dy * u_over_z1) + (w2_dy * u_over_z2)) * inv_area;
357 const float v_over_z_dx = ((w0_dx * v_over_z0) + (w1_dx * v_over_z1) + (w2_dx * v_over_z2)) * inv_area;
358 const float v_over_z_dy = ((w0_dy * v_over_z0) + (w1_dy * v_over_z1) + (w2_dy * v_over_z2)) * inv_area;
360 for (
int y = min_y; y <= max_y; ++y) {
364 float inv_z = row_inv_z;
365 float u_over_z = row_u_over_z;
366 float v_over_z = row_v_over_z;
368 for (
int x = min_x; x <= max_x; ++x) {
369 if ((positive_area && w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) || (!positive_area && w0 <= 0.0f && w1 <= 0.0f && w2 <= 0.0f)) {
371 const float reciprocal_z = 1.0f / inv_z;
372 const float u = u_over_z * reciprocal_z;
373 const float v = v_over_z * reciprocal_z;
374 put_shaded_pixel_unchecked(x, y, texture.sample_nearest(u, v), fixed_intensity);
382 u_over_z += u_over_z_dx;
383 v_over_z += v_over_z_dx;
389 row_inv_z += inv_z_dy;
390 row_u_over_z += u_over_z_dy;
391 row_v_over_z += v_over_z_dy;
395 static mxvk::vec4D project_to_screen(
const mxvk::vec4D &point,
int width,
int height) {
396 const float scale =
static_cast<float>(std::min(width, height)) * 0.52f;
397 const float center_x =
static_cast<float>(width) * 0.5f;
398 const float center_y =
static_cast<float>(height) * 0.5f;
399 const float z = std::max(point.
z, 0.001f);
400 return {center_x + (point.
x / z) * scale, center_y - (point.
y / z) * scale, point.
z, 1.0f};
405int main(
int argc,
char **argv) {
411 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
414 std::cerr << std::format(
"mxvk: Argument Exception: {}\n", e.
text());
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Exception thrown by Argz::proc() on unrecognised or malformed options.
void operator()(SDL_Surface *surface) const
void event(SDL_Event &e) override
Handle one SDL event.
void proc() override
Execute one processing/update step.
Math3DTextureArrayWindow(const Arguments &args, const std::string &title)
Four-by-four homogeneous transform matrix.
void BuildXYZ(float theta_x, float theta_y, float theta_z)
Build an XYZ Euler rotation matrix from angles in degrees.
vec4D MulVec(const vec4D &in) const
Transform a homogeneous 4D vector by this matrix.
Main Vulkan window wrapper for MXVK.
void loop()
Run the main event/render loop.
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
VkExtent2D swapchain_extent
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
void exit()
Request loop termination.
VK_Window()=default
Construct an empty window object.
Two-dimensional float vector with common arithmetic helpers.
Three-dimensional float vector with arithmetic, dot, and cross-product helpers.
void Normalize()
Normalize this vector in place, or reset it to zero if it is too short.
Four-dimensional float vector used for homogeneous 3D coordinates.
void Normalize()
Normalize the 3D components in place and reset W to 1.
constexpr float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
void Build(const vec4D &to)
Replace this vector with the direction from this point to to.
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
PNG image loading and saving utilities via SDL3.
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps)
Utilities for loading and saving PNG images.
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
void BuildTables()
Rebuild the sine and cosine lookup tables.
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
float edge_function(const vec2D &a, const vec2D &b, const vec2D &p)
Compute the signed edge function value for point p relative to edge a-b.
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
std::string texture
Optional texture file path (--texture).
std::string filename
Optional input filename (--filename).
std::string path
Asset root; proc_args() defaults it to the executable directory.
std::array< TexVertex, 4 > vertices
mxvk::MXCOLOR sample(float u, float v) const
std::vector< mxvk::MXCOLOR > pixels
mxvk::MXCOLOR sample_nearest(float u, float v) const