4#if defined(MXVK_USE_EIGEN_MATH)
25 class SurfaceDeleter {
32 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
34 SurfacePtr create_frame_surface(
int width,
int height) {
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;
80 [[nodiscard]] std::string resolve_texture_path(
const Arguments &args) {
82 if (texture_path.empty()) {
83 throw mxvk::Exception(
"3dmath_texture_array: pass a PNG with --filename <file.png> or --texture <file.png>");
86 namespace fs = std::filesystem;
87 fs::path requested(texture_path);
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.
int main(int argc, char **argv)
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