4#if defined(MXVK_USE_EIGEN_MATH)
25 class SurfaceDeleter {
32 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
35 SurfacePtr
surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
37 throw mxvk::Exception(std::format(
"Failed to create 3dmath_texture_array frame surface: {}", SDL_GetError()));
45 std::vector<mxvk::MXCOLOR>
pixels;
52 u = std::clamp(u, 0.0f, 1.0f);
53 v = std::clamp(v, 0.0f, 1.0f);
54 const int x = std::clamp(
static_cast<int>(u *
static_cast<float>(
width - 1) + 0.5f), 0,
width - 1);
55 const int y = std::clamp(
static_cast<int>(v *
static_cast<float>(
height - 1) + 0.5f), 0,
height - 1);
56 return pixels[
static_cast<std::size_t
>(y *
width + x)];
60 u = std::clamp(u, 0.0f, 1.0f);
61 v = std::clamp(v, 0.0f, 1.0f);
62 const int x =
static_cast<int>(u *
static_cast<float>(
width - 1) + 0.5f);
63 const int y =
static_cast<int>(v *
static_cast<float>(
height - 1) + 0.5f);
64 return pixels[
static_cast<std::size_t
>(y *
width + x)];
77 float intensity = 1.0f;
83 throw mxvk::Exception(
"3dmath_texture_array: pass a PNG with --filename <file.png> or --texture <file.png>");
86 namespace fs = std::filesystem;
88 if (requested.is_absolute() || fs::exists(requested)) {
89 return requested.string();
92 if (!args.
path.empty()) {
93 const fs::path from_asset_path = fs::path(args.
path) / requested;
94 if (fs::exists(from_asset_path)) {
95 return from_asset_path.string();
99 return requested.string();
102 [[nodiscard]] Texture
load_texture(
const std::string &path) {
105 throw mxvk::Exception(std::format(
"3dmath_texture_array: failed to load PNG '{}'", path));
108 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
110 throw mxvk::Exception(std::format(
"3dmath_texture_array: failed to convert PNG '{}': {}", path, SDL_GetError()));
113 const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(rgba->format);
114 if (format ==
nullptr) {
115 throw mxvk::Exception(std::format(
"3dmath_texture_array: failed to query PNG format '{}': {}", path, SDL_GetError()));
119 texture.width = rgba->w;
120 texture.height = rgba->h;
121 texture.pixels.resize(
static_cast<std::size_t
>(texture.width * texture.height));
123 for (
int y = 0; y < texture.height; ++y) {
124 const auto *row =
static_cast<const std::uint8_t *
>(rgba->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(rgba->pitch));
125 const auto *src =
reinterpret_cast<const std::uint32_t *
>(row);
126 for (
int x = 0; x < texture.width; ++x) {
131 SDL_GetRGBA(src[x], format,
nullptr, &r, &g, &b, &a);
132 texture.pixels[
static_cast<std::size_t
>(y * texture.width + x)] =
149 texture(load_texture(resolve_texture_path(args))),
150 frame_width(args.framebuffer.width),
151 frame_height(args.framebuffer.height),
152 fallback_width(args.width),
153 fallback_height(args.height) {
159 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
162 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
163 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
164 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
172 ensure_framebuffer();
173 if (frame_sprite ==
nullptr || frame_surface ==
nullptr || frame_format ==
nullptr) {
177 const float time =
static_cast<float>(SDL_GetTicks()) * 0.001f;
180 const std::array<mxvk::vec4D, 8> cube_vertices = {
181 mxvk::vec4D{-CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
182 mxvk::vec4D{CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
183 mxvk::vec4D{CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
184 mxvk::vec4D{-CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, 1.0f},
185 mxvk::vec4D{-CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
186 mxvk::vec4D{CUBE_HALF_EXTENT, -CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
187 mxvk::vec4D{CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
188 mxvk::vec4D{-CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, CUBE_HALF_EXTENT, 1.0f},
192 rotation.
BuildXYZ(time * 31.0f, time * 43.0f, time * 17.0f);
194 const std::array<std::array<int, 4>, 6> cube_faces = {{
206 std::vector<FaceDraw> faces;
207 faces.reserve(GRID_CUBE_COUNT * cube_faces.size());
209 for (
int grid_z = -GRID_RADIUS; grid_z <= GRID_RADIUS; ++grid_z) {
210 for (
int grid_y = -GRID_RADIUS; grid_y <= GRID_RADIUS; ++grid_y) {
211 for (
int grid_x = -GRID_RADIUS; grid_x <= GRID_RADIUS; ++grid_x) {
213 static_cast<float>(grid_x) * GRID_SPACING,
214 static_cast<float>(grid_y) * GRID_SPACING,
215 static_cast<float>(grid_z) * GRID_SPACING,
218 std::array<mxvk::vec4D, 8> camera_vertices{};
219 std::array<mxvk::vec4D, 8> projected{};
220 for (std::size_t i = 0; i < cube_vertices.size(); ++i) {
222 point.
z += camera_distance;
223 camera_vertices[i] = point;
224 projected[i] = project_to_screen(point, frame_width, frame_height);
227 for (
const auto &indices : cube_faces) {
228 const auto index0 =
static_cast<std::size_t
>(indices[0]);
229 const auto index1 =
static_cast<std::size_t
>(indices[1]);
230 const auto index2 =
static_cast<std::size_t
>(indices[2]);
231 const auto index3 =
static_cast<std::size_t
>(indices[3]);
238 const mxvk::vec4D center = (a + b + c + camera_vertices[index3]) * 0.25f;
239 const mxvk::vec4D view_vector(-center.
x, -center.
y, -center.
z, 1.0f);
247 {projected[index0], {0.0f, 1.0f}, camera_vertices[index0].z},
248 {projected[index1], {1.0f, 1.0f}, camera_vertices[index1].z},
249 {projected[index2], {1.0f, 0.0f}, camera_vertices[index2].z},
250 {projected[index3], {0.0f, 0.0f}, camera_vertices[index3].z},
252 face.depth = center.
z;
253 face.intensity = std::clamp(0.35f + diffuse * 0.65f, 0.0f, 1.0f);
254 faces.push_back(face);
260 std::ranges::sort(faces, [](
const FaceDraw &left,
const FaceDraw &right) {
261 return left.depth > right.depth;
264 for (
const FaceDraw &face : faces) {
265 draw_textured_triangle(face.vertices[0], face.vertices[1], face.vertices[2], face.intensity);
266 draw_textured_triangle(face.vertices[0], face.vertices[2], face.vertices[3], face.intensity);
269 frame_sprite->updateTexture(frame_surface->pixels, frame_width, frame_height, frame_surface->pitch);
270 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
275 SurfacePtr frame_surface;
276 const SDL_PixelFormatDetails *frame_format =
nullptr;
278 int frame_width = 1280;
279 int frame_height = 720;
280 int fallback_width = 1280;
281 int fallback_height = 720;
282 float camera_distance = 8.5f;
283 static constexpr int GRID_RADIUS = 1;
284 static constexpr std::size_t
GRID_WIDTH =
static_cast<std::size_t
>((GRID_RADIUS * 2) + 1);
286 static constexpr float CUBE_HALF_EXTENT = 0.52f;
287 static constexpr float GRID_SPACING = 1.45f;
288 static constexpr float MIN_CAMERA_DISTANCE = 5.0f;
289 static constexpr float MAX_CAMERA_DISTANCE = 18.0f;
290 static constexpr float CAMERA_ZOOM_STEP = 0.65f;
292 void ensure_framebuffer() {
293 if (frame_surface !=
nullptr) {
297 frame_surface = create_frame_surface(frame_width, frame_height);
298 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
299 if (frame_format ==
nullptr) {
300 throw mxvk::Exception(std::format(
"Failed to query 3dmath_texture_array frame format: {}", SDL_GetError()));
305 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
308 [[nodiscard]] std::uint32_t map_color(
mxvk::MXCOLOR color)
const {
313 SDL_FillSurfaceRect(frame_surface.get(),
nullptr, map_color(color));
316 void put_shaded_pixel_unchecked(
int x,
int y,
mxvk::MXCOLOR color, std::uint16_t intensity) {
317 auto *row =
static_cast<std::uint8_t *
>(frame_surface->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_surface->pitch));
318 auto *pixel = row + (
static_cast<std::size_t
>(x) * 4U);
319 pixel[0] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_r(color)) * intensity) >> 8U);
320 pixel[1] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_g(color)) * intensity) >> 8U);
321 pixel[2] =
static_cast<std::uint8_t
>((
static_cast<std::uint16_t
>(
mxvk::color_b(color)) * intensity) >> 8U);
325 void draw_textured_triangle(
const TexVertex &a,
const TexVertex &b,
const TexVertex &c,
float intensity) {
326 if (texture.width <= 0 || texture.height <= 0 || texture.pixels.empty()) {
330 const mxvk::vec2D p0(a.position.x, a.position.y);
331 const mxvk::vec2D p1(b.position.x, b.position.y);
332 const mxvk::vec2D p2(c.position.x, c.position.y);
337 const bool positive_area = area > 0.0f;
339 const int min_x = std::max(0,
static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x}))));
340 const int max_x = std::min(frame_width - 1,
static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x}))));
341 const int min_y = std::max(0,
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y}))));
342 const int max_y = std::min(frame_height - 1,
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y}))));
344 if (min_x > max_x || min_y > max_y) {
348 const float inv_area = 1.0f / area;
349 const float inv_z0 = 1.0f / std::max(a.depth, 0.001f);
350 const float inv_z1 = 1.0f / std::max(b.depth, 0.001f);
351 const float inv_z2 = 1.0f / std::max(c.depth, 0.001f);
352 const float u_over_z0 = a.uv.x * inv_z0;
353 const float u_over_z1 = b.uv.x * inv_z1;
354 const float u_over_z2 = c.uv.x * inv_z2;
355 const float v_over_z0 = a.uv.y * inv_z0;
356 const float v_over_z1 = b.uv.y * inv_z1;
357 const float v_over_z2 = c.uv.y * inv_z2;
358 const std::uint16_t fixed_intensity =
static_cast<std::uint16_t
>(std::clamp(intensity, 0.0f, 1.0f) * 256.0f);
360 const float w0_dx = p2.y - p1.y;
361 const float w0_dy = -(p2.x - p1.x);
362 const float w1_dx = p0.y - p2.y;
363 const float w1_dy = -(p0.x - p2.x);
364 const float w2_dx = p1.y - p0.y;
365 const float w2_dy = -(p1.x - p0.x);
367 const mxvk::vec2D row_start(
static_cast<float>(min_x) + 0.5f,
static_cast<float>(min_y) + 0.5f);
371 float row_inv_z = ((row_w0 * inv_z0) + (row_w1 * inv_z1) + (row_w2 * inv_z2)) * inv_area;
372 float row_u_over_z = ((row_w0 * u_over_z0) + (row_w1 * u_over_z1) + (row_w2 * u_over_z2)) * inv_area;
373 float row_v_over_z = ((row_w0 * v_over_z0) + (row_w1 * v_over_z1) + (row_w2 * v_over_z2)) * inv_area;
375 const float inv_z_dx = ((w0_dx * inv_z0) + (w1_dx * inv_z1) + (w2_dx * inv_z2)) * inv_area;
376 const float inv_z_dy = ((w0_dy * inv_z0) + (w1_dy * inv_z1) + (w2_dy * inv_z2)) * inv_area;
377 const float u_over_z_dx = ((w0_dx * u_over_z0) + (w1_dx * u_over_z1) + (w2_dx * u_over_z2)) * inv_area;
378 const float u_over_z_dy = ((w0_dy * u_over_z0) + (w1_dy * u_over_z1) + (w2_dy * u_over_z2)) * inv_area;
379 const float v_over_z_dx = ((w0_dx * v_over_z0) + (w1_dx * v_over_z1) + (w2_dx * v_over_z2)) * inv_area;
380 const float v_over_z_dy = ((w0_dy * v_over_z0) + (w1_dy * v_over_z1) + (w2_dy * v_over_z2)) * inv_area;
382 for (
int y = min_y; y <= max_y; ++y) {
386 float inv_z = row_inv_z;
387 float u_over_z = row_u_over_z;
388 float v_over_z = row_v_over_z;
390 for (
int x = min_x; x <= max_x; ++x) {
391 if ((positive_area && w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) ||
392 (!positive_area && w0 <= 0.0f && w1 <= 0.0f && w2 <= 0.0f)) {
394 const float reciprocal_z = 1.0f / inv_z;
395 const float u = u_over_z * reciprocal_z;
396 const float v = v_over_z * reciprocal_z;
397 put_shaded_pixel_unchecked(x, y, texture.sample_nearest(u, v), fixed_intensity);
405 u_over_z += u_over_z_dx;
406 v_over_z += v_over_z_dx;
412 row_inv_z += inv_z_dy;
413 row_u_over_z += u_over_z_dy;
414 row_v_over_z += v_over_z_dy;
418 static mxvk::vec4D project_to_screen(
const mxvk::vec4D &point,
int width,
int height) {
419 const float scale =
static_cast<float>(std::min(width, height)) * 0.52f;
420 const float center_x =
static_cast<float>(width) * 0.5f;
421 const float center_y =
static_cast<float>(height) * 0.5f;
422 const float z = std::max(point.
z, 0.001f);
423 return {center_x + (point.
x / z) * scale, center_y - (point.
y / z) * scale, point.
z, 1.0f};
428int main(
int argc,
char **argv) {
434 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
437 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
std::string resolve_texture_path(const Arguments &args)
std::filesystem::path texture_path(const std::string &filename, const std::string &asset_path)
Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps)
SurfacePtr create_frame_surface(int width, int height)
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