MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_math_eigen.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "mxvk_math_obj.hpp"
4#include "mxvk_sprite.hpp"
5
6#include <Eigen/Dense>
7#include <SDL3/SDL.h>
8
9#include <algorithm>
10#include <array>
11#include <cmath>
12#include <cstdint>
13#include <cstdlib>
14#include <fstream>
15#include <functional>
16#include <iomanip>
17#include <iostream>
18#include <limits>
19#include <span>
20#include <sstream>
21#include <stdexcept>
22#include <string>
23#include <type_traits>
24#include <unordered_map>
25#include <utility>
26#include <vector>
27
28/**
29 * @file mxvk_math.h
30 * @brief Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
31 */
32
33namespace mxvk {
34
35 /// Mathematical constant pi as a single-precision value.
36 inline constexpr float PI = 3.14159265358979323846f;
37
38 /// Default tolerance used for floating-point singularity and zero-length checks.
39 inline constexpr float EPSILON = 1.0e-5f;
40
41 /// Packed 32-bit color in ARGB byte order.
42 using MXCOLOR = std::uint32_t;
43
44 /**
45 * @brief Build an opaque ARGB color from red, green, and blue components.
46 * @param r Red component in the low 8 bits.
47 * @param g Green component in the low 8 bits.
48 * @param b Blue component in the low 8 bits.
49 * @return Packed color with alpha set to 255.
50 */
51 [[nodiscard]] inline constexpr MXCOLOR MXVK_RGB(int r, int g, int b) { return 0xFF000000u | ((static_cast<MXCOLOR>(r) & 0xFFu) << 16u) | ((static_cast<MXCOLOR>(g) & 0xFFu) << 8u) | (static_cast<MXCOLOR>(b) & 0xFFu); }
52
53 /// Extract the red component from a packed ARGB color.
54 [[nodiscard]] inline constexpr std::uint8_t color_r(MXCOLOR color) { return static_cast<std::uint8_t>((color >> 16u) & 0xFFu); }
55
56 /// Extract the green component from a packed ARGB color.
57 [[nodiscard]] inline constexpr std::uint8_t color_g(MXCOLOR color) { return static_cast<std::uint8_t>((color >> 8u) & 0xFFu); }
58
59 /// Extract the blue component from a packed ARGB color.
60 [[nodiscard]] inline constexpr std::uint8_t color_b(MXCOLOR color) { return static_cast<std::uint8_t>(color & 0xFFu); }
61
62 /// Extract the alpha component from a packed ARGB color.
63 [[nodiscard]] inline constexpr std::uint8_t color_a(MXCOLOR color) { return static_cast<std::uint8_t>((color >> 24u) & 0xFFu); }
64
65 /**
66 * @brief Scale the RGB channels of a color while preserving alpha.
67 * @param color Packed ARGB color to shade.
68 * @param intensity Multiplier clamped to the range [0, 1].
69 * @return Shaded packed ARGB color.
70 */
71 [[nodiscard]] inline MXCOLOR shade_color(MXCOLOR color, float intensity) {
72 intensity = std::clamp(intensity, 0.0f, 1.0f);
73 const auto scale = [intensity](std::uint8_t component) { return static_cast<int>(std::clamp(static_cast<float>(component) * intensity, 0.0f, 255.0f)); };
74 return (static_cast<MXCOLOR>(color_a(color)) << 24u) | ((static_cast<MXCOLOR>(scale(color_r(color))) & 0xFFu) << 16u) | ((static_cast<MXCOLOR>(scale(color_g(color))) & 0xFFu) << 8u) | (static_cast<MXCOLOR>(scale(color_b(color))) & 0xFFu);
75 }
76
77 inline std::array<float, 361> build_sin_table() {
78 std::array<float, 361> values{};
79 for (int ang = 0; ang <= 360; ++ang) {
80 values[static_cast<std::size_t>(ang)] = std::sin(static_cast<float>(ang) * PI / 180.0f);
81 }
82 return values;
83 }
84
85 inline std::array<float, 361> build_cos_table() {
86 std::array<float, 361> values{};
87 for (int ang = 0; ang <= 360; ++ang) {
88 values[static_cast<std::size_t>(ang)] = std::cos(static_cast<float>(ang) * PI / 180.0f);
89 }
90 return values;
91 }
92
93 /// Sine lookup table with one entry per degree from 0 through 360.
94 inline std::array<float, 361> sin_look = build_sin_table();
95
96 /// Cosine lookup table with one entry per degree from 0 through 360.
97 inline std::array<float, 361> cos_look = build_cos_table();
98
99 /// Rebuild the sine and cosine lookup tables.
100 inline void BuildTables() {
101 std::cout << "mxvk_math_eigen: building trigonometric lookup tables\n";
102 for (int ang = 0; ang <= 360; ++ang) {
103 const float theta = static_cast<float>(ang) * PI / 180.0f;
104 cos_look[static_cast<std::size_t>(ang)] = std::cos(theta);
105 sin_look[static_cast<std::size_t>(ang)] = std::sin(theta);
106 }
107 std::cout << "mxvk_math_eigen: trigonometric lookup tables ready (361 entries)\n";
108 }
109
110 /// Convert degrees to radians.
111 [[nodiscard]] inline float deg2rad(float ang) { return ang * PI / 180.0f; }
112
113 /// Convert radians to degrees.
114 [[nodiscard]] inline float rad2deg(float rad) { return rad * 180.0f / PI; }
115
116 /**
117 * @brief Approximate cosine using the degree lookup table with linear interpolation.
118 * @param theta_degrees Angle in degrees.
119 * @return Approximate cosine of the angle.
120 */
121 [[nodiscard]] inline float fast_cosf(float theta_degrees) {
122 if (!std::isfinite(theta_degrees)) {
123 return std::numeric_limits<float>::quiet_NaN();
124 }
125 theta_degrees = std::fmod(theta_degrees, 360.0f);
126 if (theta_degrees < 0.0f) {
127 theta_degrees += 360.0f;
128 }
129 if (theta_degrees >= 360.0f) {
130 theta_degrees = 0.0f;
131 }
132 const int theta_int = static_cast<int>(theta_degrees);
133 const int next_index = (theta_int + 1) % 360;
134 const float theta_frac = theta_degrees - static_cast<float>(theta_int);
135 return cos_look[static_cast<std::size_t>(theta_int)] + theta_frac * (cos_look[static_cast<std::size_t>(next_index)] - cos_look[static_cast<std::size_t>(theta_int)]);
136 }
137
138 /**
139 * @brief Approximate sine using the degree lookup table with linear interpolation.
140 * @param theta_degrees Angle in degrees.
141 * @return Approximate sine of the angle.
142 */
143 [[nodiscard]] inline float fast_sinf(float theta_degrees) {
144 if (!std::isfinite(theta_degrees)) {
145 return std::numeric_limits<float>::quiet_NaN();
146 }
147 theta_degrees = std::fmod(theta_degrees, 360.0f);
148 if (theta_degrees < 0.0f) {
149 theta_degrees += 360.0f;
150 }
151 if (theta_degrees >= 360.0f) {
152 theta_degrees = 0.0f;
153 }
154 const int theta_int = static_cast<int>(theta_degrees);
155 const int next_index = (theta_int + 1) % 360;
156 const float theta_frac = theta_degrees - static_cast<float>(theta_int);
157 return sin_look[static_cast<std::size_t>(theta_int)] + theta_frac * (sin_look[static_cast<std::size_t>(next_index)] - sin_look[static_cast<std::size_t>(theta_int)]);
158 }
159
160 /**
161 * @brief Return a pseudo-random integer in the inclusive range between two bounds.
162 * @param x First bound.
163 * @param y Second bound.
164 * @return Random integer in [min(x, y), max(x, y)].
165 */
166 [[nodiscard]] inline int rrand(int x, int y) {
167 if (x > y) {
168 std::swap(x, y);
169 }
170 return x + (std::rand() % (y - x + 1));
171 }
172
173 /// Two-dimensional float vector with common arithmetic helpers.
174 class vec2D {
175 public:
176 /// X coordinate.
177 float x = 0.0f;
178
179 /// Y coordinate.
180 float y = 0.0f;
181
182 /// Construct the zero vector.
183 constexpr vec2D() : x(0.0f), y(0.0f) {}
184
185 /// Construct a vector from explicit coordinates.
186 constexpr vec2D(float x_value, float y_value) : x(x_value), y(y_value) {}
187
188 /// Set both vector coordinates.
189 void Set(float x_value, float y_value) {
190 x = x_value;
191 y = y_value;
192 }
193
194 vec2D &operator=(const vec2D &) = default;
195
196 /// Add two vectors component-wise.
197 [[nodiscard]] vec2D operator+(const vec2D &v) const { return FromEigen(ToEigen() + v.ToEigen()); }
198
199 /// Add another vector to this vector.
200 vec2D &operator+=(const vec2D &v) {
201 FromEigen(ToEigen() + v.ToEigen(), *this);
202 return *this;
203 }
204
205 /// Subtract two vectors component-wise.
206 [[nodiscard]] vec2D operator-(const vec2D &v) const { return FromEigen(ToEigen() - v.ToEigen()); }
207
208 /// Subtract another vector from this vector.
209 vec2D &operator-=(const vec2D &v) {
210 FromEigen(ToEigen() - v.ToEigen(), *this);
211 return *this;
212 }
213
214 /// Scale this vector by a scalar.
215 [[nodiscard]] vec2D operator*(float k) const { return FromEigen(ToEigen() * k); }
216
217 /// Return a scaled copy of this vector.
218 [[nodiscard]] vec2D Scale(float k) const { return *this * k; }
219
220 /// Scale this vector in place.
221 void ScaleThis(float k) { FromEigen(ToEigen() * k, *this); }
222
223 /// Compute the dot product with another vector.
224 [[nodiscard]] float DotProduct(const vec2D &v) const { return ToEigen().dot(v.ToEigen()); }
225
226 /// Compute the Euclidean length of this vector.
227 [[nodiscard]] float Length() const { return ToEigen().norm(); }
228
229 /// Normalize this vector in place, or reset it to zero if it is too short.
230 void Normalize() {
231 const float length = Length();
232 if (length <= EPSILON) {
233 FromEigen(Eigen::Vector2f::Zero(), *this);
234 return;
235 }
236 FromEigen(ToEigen().normalized(), *this);
237 }
238
239 /// Write a normalized copy of this vector to @p v.
240 void Normalize(vec2D &v) const {
241 v = *this;
242 v.Normalize();
243 }
244
245 /// Compute the cosine of the angle between this vector and another vector.
246 [[nodiscard]] float Cos(const vec2D &v) const {
247 const float denom = Length() * v.Length();
248 return denom <= EPSILON ? 0.0f : std::clamp(DotProduct(v) / denom, -1.0f, 1.0f);
249 }
250
251 /// Format this vector as a named angle-bracket tuple.
252 [[nodiscard]] std::string Print(const std::string &name = "v") const {
253 std::ostringstream out;
254 out << name << '<' << x << ',' << y << '>';
255 return out.str();
256 }
257
258 private:
259 [[nodiscard]] Eigen::Vector2f ToEigen() const { return {x, y}; }
260
261 [[nodiscard]] static vec2D FromEigen(const Eigen::Vector2f &v) { return {v.x(), v.y()}; }
262
263 static void FromEigen(const Eigen::Vector2f &v, vec2D &out) { out.Set(v.x(), v.y()); }
264 };
265
266 /// Write a 2D vector to a stream using vec2D::Print().
267 inline std::ostream &operator<<(std::ostream &out, const vec2D &v) { return out << v.Print(); }
268
269 /// Read a 2D vector from a stream as two scalar coordinates.
270 inline std::istream &operator>>(std::istream &in, vec2D &v) { return in >> v.x >> v.y; }
271
272 /// Three-dimensional float vector with arithmetic, dot, and cross-product helpers.
273 class vec3D {
274 public:
275 /// X coordinate.
276 float x = 0.0f;
277
278 /// Y coordinate.
279 float y = 0.0f;
280
281 /// Z coordinate.
282 float z = 0.0f;
283
284 /// Construct the zero vector.
285 constexpr vec3D() : x(0.0f), y(0.0f), z(0.0f) {}
286
287 /// Construct a vector from explicit coordinates.
288 constexpr vec3D(float x_value, float y_value, float z_value) : x(x_value), y(y_value), z(z_value) {}
289
290 /// Set all vector coordinates.
291 void Set(float x_value, float y_value, float z_value) {
292 x = x_value;
293 y = y_value;
294 z = z_value;
295 }
296
297 vec3D &operator=(const vec3D &) = default;
298
299 /// Add two vectors component-wise.
300 [[nodiscard]] vec3D operator+(const vec3D &v) const { return FromEigen(ToEigen() + v.ToEigen()); }
301
302 /// Add another vector to this vector.
303 vec3D &operator+=(const vec3D &v) {
304 FromEigen(ToEigen() + v.ToEigen(), *this);
305 return *this;
306 }
307
308 /// Subtract two vectors component-wise.
309 [[nodiscard]] vec3D operator-(const vec3D &v) const { return FromEigen(ToEigen() - v.ToEigen()); }
310
311 /// Subtract another vector from this vector.
312 vec3D &operator-=(const vec3D &v) {
313 FromEigen(ToEigen() - v.ToEigen(), *this);
314 return *this;
315 }
316
317 /// Scale this vector by a scalar.
318 [[nodiscard]] vec3D operator*(float k) const { return FromEigen(ToEigen() * k); }
319
320 /// Return a scaled copy of this vector.
321 [[nodiscard]] vec3D Scale(float k) const { return *this * k; }
322
323 /// Scale this vector in place.
324 void ScaleThis(float k) { FromEigen(ToEigen() * k, *this); }
325
326 /// Compute the dot product with another vector.
327 [[nodiscard]] float DotProduct(const vec3D &v) const { return ToEigen().dot(v.ToEigen()); }
328
329 /// Compute the right-handed cross product with another vector.
330 [[nodiscard]] vec3D CrossProduct(const vec3D &v) const { return FromEigen(ToEigen().cross(v.ToEigen())); }
331
332 /// Compute the Euclidean length of this vector.
333 [[nodiscard]] float Length() const { return ToEigen().norm(); }
334
335 /// Normalize this vector in place, or reset it to zero if it is too short.
336 void Normalize() {
337 const float len = Length();
338 if (len <= EPSILON) {
339 FromEigen(Eigen::Vector3f::Zero(), *this);
340 return;
341 }
342 FromEigen(ToEigen().normalized(), *this);
343 }
344
345 /// Write a normalized copy of this vector to @p v.
346 void Normalize(vec3D &v) const {
347 v = *this;
348 v.Normalize();
349 }
350
351 /// Compute the cosine of the angle between this vector and another vector.
352 [[nodiscard]] float Cos(const vec3D &v) const {
353 const float denom = Length() * v.Length();
354 return denom <= EPSILON ? 0.0f : std::clamp(DotProduct(v) / denom, -1.0f, 1.0f);
355 }
356
357 /// Format this vector as a named angle-bracket tuple.
358 [[nodiscard]] std::string Print(const std::string &name = "v") const {
359 std::ostringstream out;
360 out << name << '<' << x << ',' << y << ',' << z << '>';
361 return out.str();
362 }
363
364 private:
365 [[nodiscard]] Eigen::Vector3f ToEigen() const { return {x, y, z}; }
366
367 [[nodiscard]] static vec3D FromEigen(const Eigen::Vector3f &v) { return {v.x(), v.y(), v.z()}; }
368
369 static void FromEigen(const Eigen::Vector3f &v, vec3D &out) { out.Set(v.x(), v.y(), v.z()); }
370 };
371
372 /// Write a 3D vector to a stream using vec3D::Print().
373 inline std::ostream &operator<<(std::ostream &out, const vec3D &v) { return out << v.Print(); }
374
375 /// Read a 3D vector from a stream as three scalar coordinates.
376 inline std::istream &operator>>(std::istream &in, vec3D &v) { return in >> v.x >> v.y >> v.z; }
377
378 /// Four-dimensional float vector used for homogeneous 3D coordinates.
379 class vec4D {
380 public:
381 /// X coordinate.
382 float x = 0.0f;
383
384 /// Y coordinate.
385 float y = 0.0f;
386
387 /// Z coordinate.
388 float z = 0.0f;
389
390 /// Homogeneous W coordinate.
391 float w = 1.0f;
392
393 /// Construct the homogeneous origin.
394 constexpr vec4D() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}
395
396 /// Construct a homogeneous vector from explicit coordinates.
397 constexpr vec4D(float x_value, float y_value, float z_value, float w_value = 1.0f) : x(x_value), y(y_value), z(z_value), w(w_value) {}
398
399 /// Set all vector coordinates.
400 void Set(float x_value, float y_value, float z_value, float w_value = 1.0f) {
401 x = x_value;
402 y = y_value;
403 z = z_value;
404 w = w_value;
405 }
406
407 /// Copy coordinates from another vector.
408 void Set(const vec4D &v) { *this = v; }
409
410 vec4D &operator=(const vec4D &) = default;
411
412 /// Add two vectors component-wise.
413 [[nodiscard]] vec4D operator+(const vec4D &v) const { return {x + v.x, y + v.y, z + v.z, w + v.w}; }
414
415 /// Add another vector to this vector.
416 vec4D &operator+=(const vec4D &v) {
417 x += v.x;
418 y += v.y;
419 z += v.z;
420 w += v.w;
421 return *this;
422 }
423
424 /// Subtract two vectors component-wise.
425 [[nodiscard]] vec4D operator-(const vec4D &v) const { return {x - v.x, y - v.y, z - v.z, w - v.w}; }
426
427 /// Subtract another vector from this vector.
428 vec4D &operator-=(const vec4D &v) {
429 x -= v.x;
430 y -= v.y;
431 z -= v.z;
432 w -= v.w;
433 return *this;
434 }
435
436 /// Scale this vector by a scalar.
437 [[nodiscard]] vec4D operator*(float k) const { return {x * k, y * k, z * k, w * k}; }
438
439 /// Multiply two vectors component-wise.
440 [[nodiscard]] vec4D operator*(const vec4D &v) const { return {x * v.x, y * v.y, z * v.z, w * v.w}; }
441
442 /// Return a scaled copy of this vector.
443 [[nodiscard]] vec4D Scale(float k) const { return *this * k; }
444
445 /// Scale this vector in place.
446 void ScaleThis(float k) {
447 x *= k;
448 y *= k;
449 z *= k;
450 w *= k;
451 }
452
453 /// Compute the 3D dot product, ignoring the W component.
454 [[nodiscard]] float DotProduct(const vec4D &v) const { return x * v.x + y * v.y + z * v.z; }
455
456 /// Compute the 3D cross product and return it as a direction with W set to 0.
457 [[nodiscard]] vec4D CrossProduct(const vec4D &v) const {
458 return {
459 y * v.z - z * v.y,
460 z * v.x - x * v.z,
461 x * v.y - y * v.x,
462 0.0f,
463 };
464 }
465
466 /// Compute the 3D Euclidean length, ignoring the W component.
467 [[nodiscard]] float Length() const { return std::sqrt(DotProduct(*this)); }
468
469 /// Normalize the 3D components in place while preserving W.
470 void Normalize() {
471 const float len = Length();
472 if (len <= EPSILON) {
473 x = y = z = 0.0f;
474 return;
475 }
476 x /= len;
477 y /= len;
478 z /= len;
479 }
480
481 /// Write a normalized copy of this vector to @p v.
482 void Normalize(vec4D &v) const {
483 v = *this;
484 v.Normalize();
485 }
486
487 /// Compute the cosine of the angle between the 3D components of two vectors.
488 [[nodiscard]] float Cos(const vec4D &v) const {
489 const float denom = Length() * v.Length();
490 return denom <= EPSILON ? 0.0f : std::clamp(DotProduct(v) / denom, -1.0f, 1.0f);
491 }
492
493 /// Replace this vector with the direction from this point to @p to.
494 void Build(const vec4D &to) { *this = Build(*this, to); }
495
496 /// Build a direction vector from @p from to @p to with W set to 0.
497 [[nodiscard]] vec4D Build(const vec4D &from, const vec4D &to) const { return {to.x - from.x, to.y - from.y, to.z - from.z, 0.0f}; }
498
499 /// Format this vector as a named angle-bracket tuple.
500 [[nodiscard]] std::string Print(const std::string &name = "v") const {
501 std::ostringstream out;
502 out << name << '<' << x << ',' << y << ',' << z << ',' << w << '>';
503 return out.str();
504 }
505 };
506
507 /// Write a 4D vector to a stream using vec4D::Print().
508 inline std::ostream &operator<<(std::ostream &out, const vec4D &v) { return out << v.Print(); }
509
510 /// Read a 4D vector from a stream as four scalar coordinates.
511 inline std::istream &operator>>(std::istream &in, vec4D &v) { return in >> v.x >> v.y >> v.z >> v.w; }
512
513 /// Two-element column-vector storage used by 2x2 linear solves.
514 class Mat1D {
515 public:
516 /// Matrix/vector elements.
517 float mat[2]{};
518
519 /// Construct a zero-initialized 2-element vector.
520 constexpr Mat1D() = default;
521
522 /// Construct from explicit elements.
523 constexpr Mat1D(float m0, float m1) : mat{m0, m1} {}
524
525 /// Set both elements.
526 void Set(float m0, float m1) {
527 mat[0] = m0;
528 mat[1] = m1;
529 }
530 };
531
532 /// Three-element column-vector storage used by 3x3 linear solves.
533 class Mat1x3D {
534 public:
535 /// Matrix/vector elements.
536 float mat[3]{};
537
538 /// Construct a zero-initialized 3-element vector.
539 constexpr Mat1x3D() = default;
540
541 /// Construct from explicit elements.
542 constexpr Mat1x3D(float m0, float m1, float m2) : mat{m0, m1, m2} {}
543 };
544
545 /// Four-element column-vector storage.
546 class Mat1x4D {
547 public:
548 /// Matrix/vector elements.
549 float mat[4]{};
550
551 /// Construct a zero-initialized 4-element vector.
552 constexpr Mat1x4D() = default;
553
554 /// Construct from explicit elements.
555 constexpr Mat1x4D(float m0, float m1, float m2, float m3) : mat{m0, m1, m2, m3} {}
556 };
557
558 /// Four-by-three matrix storage.
559 class Mat4x3D {
560 public:
561 /// Matrix elements indexed as row, column.
562 float mat[4][3]{};
563 };
564
565 /// Two-by-two matrix with arithmetic, determinant, inverse, and solve helpers.
566 class Mat2D {
567 public:
568 /// Matrix elements indexed as row, column.
569 float mat[2][2]{};
570
571 /// Construct a zero-initialized matrix.
572 Mat2D() = default;
573
574 /// Construct from explicit row-major elements.
575 Mat2D(float m00, float m01, float m10, float m11) { Set(m00, m01, m10, m11); }
576
577 /// Set all matrix elements in row-major order.
578 void Set(float m00, float m01, float m10, float m11) { ToEigen() << m00, m01, m10, m11; }
579
580 /// Set this matrix to the identity matrix.
581 void LoadIdentity() { ToEigen().setIdentity(); }
582
583 /// Add two matrices component-wise.
584 [[nodiscard]] Mat2D operator+(const Mat2D &m) const { return FromEigen(ToEigen() + m.ToEigen()); }
585
586 /// Subtract two matrices component-wise.
587 [[nodiscard]] Mat2D operator-(const Mat2D &m) const { return FromEigen(ToEigen() - m.ToEigen()); }
588
589 /// Multiply two 2x2 matrices.
590 [[nodiscard]] Mat2D operator*(const Mat2D &m) const { return FromEigen(ToEigen() * m.ToEigen()); }
591
592 /// Compute the matrix determinant.
593 [[nodiscard]] float Determinate() const { return ToEigen().determinant(); }
594
595 /**
596 * @brief Compute the inverse matrix.
597 * @param out Receives the inverse on success.
598 * @return True when the matrix is invertible.
599 */
600 bool Inverse(Mat2D &out) const {
601 const float d = Determinate();
602 if (std::fabs(d) <= EPSILON) {
603 return false;
604 }
605 out = FromEigen(ToEigen().inverse());
606 return true;
607 }
608
609 /**
610 * @brief Solve a 2x2 linear system.
611 * @param a Coefficient matrix.
612 * @param out Receives the solution vector.
613 * @param b Right-hand-side vector.
614 * @return True when the system has a unique solution.
615 */
616 static bool Solve2x2(const Mat2D &a, Mat1D &out, const Mat1D &b) {
617 const float d = a.Determinate();
618 if (std::fabs(d) <= EPSILON) {
619 return false;
620 }
621 const Eigen::Vector2f rhs(b.mat[0], b.mat[1]);
622 const Eigen::Vector2f result = a.ToEigen().transpose().partialPivLu().solve(rhs);
623 out.Set(result.x(), result.y());
624 return true;
625 }
626
627 private:
628 using EigenMatrix = Eigen::Matrix<float, 2, 2, Eigen::RowMajor>;
629
630 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() { return Eigen::Map<EigenMatrix>(&mat[0][0]); }
631
632 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen() const { return Eigen::Map<const EigenMatrix>(&mat[0][0]); }
633
634 template <typename Derived> [[nodiscard]] static Mat2D FromEigen(const Eigen::MatrixBase<Derived> &m) {
635 Mat2D out;
636 out.ToEigen() = m;
637 return out;
638 }
639 };
640
641 /// Three-by-three matrix with multiplication, vector transform, inverse, and solve helpers.
642 class Mat3D {
643 public:
644 /// Matrix elements indexed as row, column.
645 float mat[3][3]{};
646
647 /// Construct a zero-initialized matrix.
648 Mat3D() = default;
649
650 /// Construct from explicit row-major elements.
651 Mat3D(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) { Set(m00, m01, m02, m10, m11, m12, m20, m21, m22); }
652
653 /// Set all matrix elements in row-major order.
654 void Set(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) { ToEigen() << m00, m01, m02, m10, m11, m12, m20, m21, m22; }
655
656 /// Set this matrix to the identity matrix.
657 void LoadIdentity() { ToEigen().setIdentity(); }
658
659 /// Multiply two 3x3 matrices.
660 [[nodiscard]] Mat3D operator*(const Mat3D &m) const { return FromEigen(ToEigen() * m.ToEigen()); }
661
662 /// Transform a 3D vector by this matrix.
663 [[nodiscard]] vec3D MulVec(const vec3D &in) const {
664 const Eigen::Vector3f result = ToEigen().transpose() * Eigen::Vector3f(in.x, in.y, in.z);
665 return {result.x(), result.y(), result.z()};
666 }
667
668 /// Transform a 3D vector and write the result to @p out.
669 void MulVec(const vec3D &in, vec3D &out) const { out = MulVec(in); }
670
671 /// Compute the matrix determinant.
672 [[nodiscard]] float Determinate() const { return ToEigen().determinant(); }
673
674 /**
675 * @brief Compute the inverse matrix.
676 * @param out Receives the inverse on success.
677 * @return True when the matrix is invertible.
678 */
679 bool Inverse(Mat3D &out) const {
680 const float d = Determinate();
681 if (std::fabs(d) <= EPSILON) {
682 return false;
683 }
684 out = FromEigen(ToEigen().inverse());
685 return true;
686 }
687
688 /**
689 * @brief Solve a 3x3 linear system.
690 * @param a Coefficient matrix.
691 * @param out Receives the solution vector.
692 * @param b Right-hand-side vector.
693 * @return True when the system has a unique solution.
694 */
695 static bool Solve3x3(const Mat3D &a, Mat1x3D &out, const Mat1x3D &b) {
696 if (std::fabs(a.Determinate()) <= EPSILON) {
697 return false;
698 }
699 const Eigen::Vector3f rhs(b.mat[0], b.mat[1], b.mat[2]);
700 const Eigen::Vector3f result = a.ToEigen().transpose().partialPivLu().solve(rhs);
701 Eigen::Map<Eigen::Vector3f>(out.mat) = result;
702 return true;
703 }
704
705 private:
706 using EigenMatrix = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;
707
708 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() { return Eigen::Map<EigenMatrix>(&mat[0][0]); }
709
710 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen() const { return Eigen::Map<const EigenMatrix>(&mat[0][0]); }
711
712 template <typename Derived> [[nodiscard]] static Mat3D FromEigen(const Eigen::MatrixBase<Derived> &m) {
713 Mat3D out;
714 out.ToEigen() = m;
715 return out;
716 }
717 };
718
719 /// Four-by-four homogeneous transform matrix.
720 class Mat4D {
721 public:
722 /// Matrix elements indexed as row, column.
723 float mat[4][4]{};
724
725 /// Construct a zero-initialized matrix.
726 Mat4D() = default;
727
728 /// Construct from explicit row-major elements.
729 Mat4D(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { Set(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33); }
730
731 /// Set all matrix elements in row-major order.
732 void Set(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { ToEigen() << m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; }
733
734 /// Set this matrix to the identity matrix.
735 void LoadIdentity() { ToEigen().setIdentity(); }
736
737 /// Add two matrices component-wise.
738 [[nodiscard]] Mat4D operator+(const Mat4D &m) const { return FromEigen(ToEigen() + m.ToEigen()); }
739
740 /// Multiply two 4x4 matrices.
741 [[nodiscard]] Mat4D operator*(const Mat4D &m) const { return FromEigen(ToEigen() * m.ToEigen()); }
742
743 /// Multiply this matrix by another matrix in place.
744 Mat4D &operator*=(const Mat4D &m) {
745 *this = *this * m;
746 return *this;
747 }
748
749 /// Transform a homogeneous 4D vector by this matrix.
750 [[nodiscard]] vec4D MulVec(const vec4D &in) const {
751 const Eigen::Vector4f result = ToEigen().transpose() * Eigen::Vector4f(in.x, in.y, in.z, in.w);
752 return {result.x(), result.y(), result.z(), result.w()};
753 }
754
755 /// Transform a homogeneous 4D vector and write the result to @p out.
756 void MulVec(const vec4D &in, vec4D &out) const { out = MulVec(in); }
757
758 /**
759 * @brief Transform a batch of homogeneous 4D vectors.
760 * @param input Source vectors.
761 * @param output Destination vectors, with the same size as @p input.
762 *
763 * Processing the vectors as a 4xN matrix lets Eigen use packetized SIMD
764 * across the complete batch instead of evaluating one small expression
765 * per vertex.
766 */
767 void MulVec(std::span<const vec4D> input, std::span<vec4D> output) const {
768 if (input.size() != output.size()) {
769 throw std::invalid_argument("Mat4D::MulVec batch spans must have equal sizes");
770 }
771 if (input.empty()) {
772 return;
773 }
774
775 static_assert(std::is_standard_layout_v<vec4D>);
776 static_assert(sizeof(vec4D) == sizeof(float) * 4);
777 using VertexMatrix = Eigen::Matrix<float, 4, Eigen::Dynamic, Eigen::ColMajor>;
778 const Eigen::Index vertex_count = static_cast<Eigen::Index>(input.size());
779 const Eigen::Map<const VertexMatrix, Eigen::Unaligned> input_matrix(&input.front().x, 4, vertex_count);
780 Eigen::Map<VertexMatrix, Eigen::Unaligned> output_matrix(&output.front().x, 4, vertex_count);
781 output_matrix.noalias() = ToEigen().transpose() * input_matrix;
782 }
783
784 /// Transform a 3D point by this matrix using W = 1.
785 [[nodiscard]] vec3D MulVec(const vec3D &in) const {
786 const vec4D r = MulVec(vec4D(in.x, in.y, in.z, 1.0f));
787 return {r.x, r.y, r.z};
788 }
789
790 /// Transform a 3D point and write the result to @p out.
791 void MulVec(const vec3D &in, vec3D &out) const { out = MulVec(in); }
792
793 /**
794 * @brief Compute the inverse matrix.
795 * @param out Receives the inverse on success.
796 * @return True when the matrix is invertible.
797 */
798 bool Inverse(Mat4D &out) const {
799 Eigen::FullPivLU<EigenMatrix> decomposition(ToEigen());
800 decomposition.setThreshold(EPSILON);
801 if (!decomposition.isInvertible()) {
802 return false;
803 }
804 out = FromEigen(decomposition.inverse());
805 return true;
806 }
807
808 /// Build an XYZ Euler rotation matrix from angles in degrees.
809 void BuildXYZ(float theta_x, float theta_y, float theta_z) {
810 const float cx = std::cos(deg2rad(theta_x));
811 const float sx = std::sin(deg2rad(theta_x));
812 const float cy = std::cos(deg2rad(theta_y));
813 const float sy = std::sin(deg2rad(theta_y));
814 const float cz = std::cos(deg2rad(theta_z));
815 const float sz = std::sin(deg2rad(theta_z));
816
817 Mat4D mx(1, 0, 0, 0, 0, cx, sx, 0, 0, -sx, cx, 0, 0, 0, 0, 1);
818 Mat4D my(cy, 0, -sy, 0, 0, 1, 0, 0, sy, 0, cy, 0, 0, 0, 0, 1);
819 Mat4D mz(cz, sz, 0, 0, -sz, cz, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
820 *this = mx * my * mz;
821 }
822
823 private:
824 using EigenMatrix = Eigen::Matrix<float, 4, 4, Eigen::RowMajor>;
825
826 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() { return Eigen::Map<EigenMatrix>(&mat[0][0]); }
827
828 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen() const { return Eigen::Map<const EigenMatrix>(&mat[0][0]); }
829
830 template <typename Derived> [[nodiscard]] static Mat4D FromEigen(const Eigen::MatrixBase<Derived> &m) {
831 Mat4D out;
832 out.ToEigen() = m;
833 return out;
834 }
835 };
836
837 /// Parametric 2D line segment represented by a start point, end point, and direction.
838 struct paramLine2D {
839 /// Segment start point.
840 vec2D p0;
841
842 /// Segment end point.
843 vec2D p1;
844
845 /// Direction vector, commonly p1 - p0.
846 vec2D v;
847
848 /// Construct an uninitialized line segment.
849 paramLine2D() = default;
850
851 /// Construct from explicit endpoints and direction.
852 paramLine2D(const vec2D &start, const vec2D &end, const vec2D &dir) { Set(start, end, dir); }
853
854 /// Set explicit endpoints and direction.
855 void Set(const vec2D &start, const vec2D &end, const vec2D &dir) {
856 p0 = start;
857 p1 = end;
858 v = dir;
859 }
860
861 /// Initialize from endpoints and derive the direction vector.
862 void Init(const vec2D &start, const vec2D &end) {
863 p0 = start;
864 p1 = end;
865 v = end - start;
866 }
867
868 /// Compute the point p0 + v * t.
869 [[nodiscard]] vec2D ComputePoint(float t) const { return p0 + v * t; }
870
871 /// Compute the point p0 + v * t and write it to @p out.
872 vec2D ComputePoint(float t, vec2D &out) const {
873 out = ComputePoint(t);
874 return out;
875 }
876
877 /**
878 * @brief Intersect this segment with another parametric segment.
879 * @param line Segment to intersect with.
880 * @param t_this Receives this segment's intersection parameter.
881 * @param t_other Receives the other segment's intersection parameter.
882 * @return 0 for parallel lines, 1 for segment intersection, 2 for line intersection outside one or both segments.
883 */
884 int Intersect(const paramLine2D &line, float &t_this, float &t_other) const {
885 Eigen::Matrix2f directions;
886 directions << v.x, line.v.x, v.y, line.v.y;
887 const float det = directions.determinant();
888 if (std::fabs(det) <= EPSILON) {
889 return 0;
890 }
891 const Eigen::Vector2f delta(line.p0.x - p0.x, line.p0.y - p0.y);
892 const Eigen::Vector2f parameters = directions.partialPivLu().solve(delta);
893 t_this = parameters.x();
894 t_other = -parameters.y();
895 return (t_this >= 0.0f && t_this <= 1.0f && t_other >= 0.0f && t_other <= 1.0f) ? 1 : 2;
896 }
897
898 /**
899 * @brief Intersect this segment with another segment and compute the point.
900 * @param line Segment to intersect with.
901 * @param out Receives the intersection point when the lines are not parallel.
902 * @return 0 for parallel lines, 1 for segment intersection, 2 for line intersection outside one or both segments.
903 */
904 int Intersect(const paramLine2D &line, vec2D &out) const {
905 float t_this = 0.0f;
906 float t_other = 0.0f;
907 const int result = Intersect(line, t_this, t_other);
908 if (result != 0) {
909 out = ComputePoint(t_this);
910 }
911 return result;
912 }
913 };
914
915 /// Parametric 3D line segment represented by a start point, end point, and direction.
916 struct paramLine3D {
917 /// Segment start point.
918 vec3D p0;
919
920 /// Segment end point.
921 vec3D p1;
922
923 /// Direction vector, commonly p1 - p0.
924 vec3D v;
925
926 /// Construct an uninitialized line segment.
927 paramLine3D() = default;
928
929 /// Construct from explicit endpoints and direction.
930 paramLine3D(const vec3D &start, const vec3D &end, const vec3D &dir) { Set(start, end, dir); }
931
932 /// Set explicit endpoints and direction.
933 void Set(const vec3D &start, const vec3D &end, const vec3D &dir) {
934 p0 = start;
935 p1 = end;
936 v = dir;
937 }
938
939 /// Initialize from endpoints and derive the direction vector.
940 void Init(const vec3D &start, const vec3D &end) {
941 p0 = start;
942 p1 = end;
943 v = end - start;
944 }
945
946 /// Compute the point p0 + v * t.
947 [[nodiscard]] vec3D ComputePoint(float t) const { return p0 + v * t; }
948
949 /// Compute the point p0 + v * t and write it to @p out.
950 vec3D ComputePoint(float t, vec3D &out) const {
951 out = ComputePoint(t);
952 return out;
953 }
954 };
955
956 /// Plane represented by a point and a normal vector.
957 struct Plane3D {
958 /// Point on the plane.
959 vec3D p0;
960
961 /// Plane normal.
962 vec3D v;
963
964 /// Construct an uninitialized plane.
965 Plane3D() = default;
966
967 /// Construct from a point and normal vector.
968 Plane3D(const vec3D &point, const vec3D &normal) : p0(point), v(normal) {}
969
970 /// Set the plane point and normal, optionally normalizing the normal.
971 void Set(const vec3D &point, const vec3D &normal, bool normalize) {
972 p0 = point;
973 v = normal;
974 if (normalize) {
975 v.Normalize();
976 }
977 }
978 };
979
980 /// 2D polar coordinate.
981 struct Polar {
982 /// Radial distance.
983 float r = 0.0f;
984
985 /// Angle in degrees for this framework's math helpers.
986 float theta = 0.0f;
987 };
988
989 /// Cylindrical coordinate.
990 struct CyType {
991 /// Radial distance.
992 float r = 0.0f;
993
994 /// Azimuth angle in degrees for this framework's math helpers.
995 float theta = 0.0f;
996
997 /// Height coordinate.
998 float z = 0.0f;
999 };
1000
1001 /// Spherical coordinate.
1002 struct SpType {
1003 /// Radial distance.
1004 float p = 0.0f;
1005
1006 /// Azimuth angle in degrees for this framework's math helpers.
1007 float theta = 0.0f;
1008
1009 /// Inclination angle in degrees for this framework's math helpers.
1010 float phi = 0.0f;
1011 };
1012
1013 /// Quaternion used for 3D rotations.
1014 class QuatType {
1015 public:
1016 /// X component of the vector part.
1017 float x = 0.0f;
1018
1019 /// Y component of the vector part.
1020 float y = 0.0f;
1021
1022 /// Z component of the vector part.
1023 float z = 0.0f;
1024
1025 /// Scalar component.
1026 float w = 1.0f;
1027
1028 /// Construct the identity quaternion.
1029 constexpr QuatType() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}
1030
1031 /// Construct from explicit quaternion components.
1032 constexpr QuatType(float x_value, float y_value, float z_value, float w_value) : x(x_value), y(y_value), z(z_value), w(w_value) {}
1033
1034 /// Add two quaternions component-wise.
1035 [[nodiscard]] constexpr QuatType operator+(const QuatType &q) const { return {x + q.x, y + q.y, z + q.z, w + q.w}; }
1036
1037 /// Subtract two quaternions component-wise.
1038 [[nodiscard]] constexpr QuatType operator-(const QuatType &q) const { return {x - q.x, y - q.y, z - q.z, w - q.w}; }
1039
1040 /// Multiply two quaternions.
1041 [[nodiscard]] constexpr QuatType operator*(const QuatType &q) const { return {w * q.x + x * q.w + y * q.z - z * q.y, w * q.y - x * q.z + y * q.w + z * q.x, w * q.z + x * q.y - y * q.x + z * q.w, w * q.w - x * q.x - y * q.y - z * q.z}; }
1042
1043 /// Multiply this quaternion by another quaternion in place.
1045 *this = *this * q;
1046 return *this;
1047 }
1048
1049 /// Conjugate this quaternion in place.
1050 void Conj() {
1051 x = -x;
1052 y = -y;
1053 z = -z;
1054 }
1055
1056 /// Scale all quaternion components in place.
1057 void Scale(float f) {
1058 x *= f;
1059 y *= f;
1060 z *= f;
1061 w *= f;
1062 }
1063
1064 /// Compute the squared norm.
1065 [[nodiscard]] float Norm2() const { return w * w + x * x + y * y + z * z; }
1066
1067 /// Compute the norm.
1068 [[nodiscard]] float Norm() const { return std::sqrt(Norm2()); }
1069
1070 /// Normalize this quaternion in place, or reset it to identity if it is too small.
1071 void Normalize() {
1072 const float norm = Norm();
1073 if (norm <= EPSILON) {
1074 x = y = z = 0.0f;
1075 w = 1.0f;
1076 return;
1077 }
1078 Scale(1.0f / norm);
1079 }
1080
1081 /// Invert this quaternion in place.
1082 void Inverse() {
1083 const float n2 = Norm2();
1084 if (n2 <= EPSILON) {
1085 x = y = z = 0.0f;
1086 w = 1.0f;
1087 return;
1088 }
1089 x = -x / n2;
1090 y = -y / n2;
1091 z = -z / n2;
1092 w = w / n2;
1093 }
1094
1095 /// Invert a unit quaternion by conjugating it.
1096 void InverseNormal() { Conj(); }
1097
1098 /// Return the quaternion product (*this * p1) * p2.
1099 [[nodiscard]] QuatType TripleProduct(const QuatType &p1, const QuatType &p2) const { return (*this * p1) * p2; }
1100
1101 /// Build a quaternion from an axis and angle in degrees.
1102 void vec3DthetaQuat(float theta_degrees, const vec3D &axis) {
1103 vec3D n = axis;
1104 n.Normalize();
1105 const float half = deg2rad(theta_degrees) * 0.5f;
1106 const float s = std::sin(half);
1107 x = s * n.x;
1108 y = s * n.y;
1109 z = s * n.z;
1110 w = std::cos(half);
1111 }
1112
1113 /// Build a quaternion from a 4D axis vector and angle in degrees.
1114 void vec4DthetaQuat(float theta_degrees, const vec4D &axis) {
1115 vec3D n(axis.x, axis.y, axis.z);
1116 vec3DthetaQuat(theta_degrees, n);
1117 }
1118
1119 /// Build a quaternion from Euler angles in ZYX order, with angles in degrees.
1120 void EulerZYX(float theta_x, float theta_y, float theta_z) {
1121 const float hx = deg2rad(theta_x) * 0.5f;
1122 const float hy = deg2rad(theta_y) * 0.5f;
1123 const float hz = deg2rad(theta_z) * 0.5f;
1124 const float cx = std::cos(hx);
1125 const float sx = std::sin(hx);
1126 const float cy = std::cos(hy);
1127 const float sy = std::sin(hy);
1128 const float cz = std::cos(hz);
1129 const float sz = std::sin(hz);
1130 w = cz * cy * cx + sz * sy * sx;
1131 x = cz * cy * sx - sz * sy * cx;
1132 y = cz * sy * cx + sz * cy * sx;
1133 z = sz * cy * cx - cz * sy * sx;
1134 }
1135
1136 /// Convert this quaternion to axis-angle form.
1137 void QuatToVec3D(float *theta_degrees, vec3D &axis) const {
1138 QuatType q = *this;
1139 q.Normalize();
1140 const float s = std::sqrt(std::max(0.0f, 1.0f - q.w * q.w));
1141 if (s <= EPSILON) {
1142 axis.Set(1.0f, 0.0f, 0.0f);
1143 } else {
1144 axis.Set(q.x / s, q.y / s, q.z / s);
1145 }
1146 if (theta_degrees != nullptr) {
1147 *theta_degrees = rad2deg(2.0f * std::acos(std::clamp(q.w, -1.0f, 1.0f)));
1148 }
1149 }
1150 };
1151
1152 /// Triangle primitive used by the simple software rendering pipeline.
1153 struct Triangle {
1154 /// Working vertex positions.
1155 vec4D vlist[3]{};
1156
1157 /// Transformed vertex positions.
1158 vec4D tlist[3]{};
1159
1160 /// Triangle color.
1161 MXCOLOR color = MXVK_RGB(255, 255, 255);
1162
1163 /// Application-defined polygon attributes.
1164 int attr = 0;
1165
1166 /// Polygon state flags.
1167 int state = 0;
1168
1169 /// Indices into an object's vertex arrays.
1170 int vert[3]{};
1171
1172 /// Index of the OBJ/MTL material used by this triangle, or -1.
1173 int material_index = -1;
1174
1175 /// Wavefront material name retained for later MTL replacement.
1176 std::string material_name;
1177
1178 /// Wavefront object name from the `o` section containing this triangle.
1179 std::string source_object_name;
1180 };
1181
1182 /// Object and polygon state bit flags.
1183 enum {
1184 /// Active object or polygon.
1185 MX_ACTIVE = 0x1,
1186
1187 /// Visible object or polygon.
1188 MX_VISIBLE = 0x2,
1189
1190 /// Polygon is marked as a backface.
1191 MX_BACKFACE = 0x4,
1192
1193 /// Object or polygon is culled.
1194 MX_CULLED = 0x8
1195 };
1196
1197 /// Flat list of triangles prepared for transformation and rasterization.
1198 class RenderList {
1199 public:
1200 /// Triangle storage.
1201 std::vector<Triangle> polys;
1202
1203 /// Cached polygon count matching polys.size().
1204 int num_polys = 0;
1205
1206 /// Clear all triangles from the render list.
1207 void Reset() {
1208 polys.clear();
1209 num_polys = 0;
1210 }
1211
1212 /// Transform active non-backface triangles by a matrix.
1213 void TransformRenderList(const Mat4D &mrot, int type) {
1214 if (type != 0) {
1215 return;
1216 }
1217 for (auto &poly : polys) {
1218 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
1219 continue;
1220 }
1221 for (auto &vertex : poly.vlist) {
1222 vertex = mrot.MulVec(vertex);
1223 }
1224 }
1225 }
1226
1227 /// Mark back-facing triangles relative to a view position.
1228 void RemoveFaces(const vec4D &pos) {
1229 for (auto &poly : polys) {
1230 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
1231 continue;
1232 }
1233 const vec4D u = vec4D().Build(poly.tlist[0], poly.tlist[1]);
1234 const vec4D v = vec4D().Build(poly.tlist[0], poly.tlist[2]);
1235 const vec4D n = u.CrossProduct(v);
1236 const vec4D view = vec4D().Build(poly.tlist[0], pos);
1237 if (n.DotProduct(view) <= 0.0f) {
1238 poly.state |= MX_BACKFACE;
1239 }
1240 }
1241 }
1242
1243 /// Translate model-space vertices into world-space transformed vertices.
1244 void ModelToWorld(const vec4D &pos, int type) {
1245 if (type != 0) {
1246 return;
1247 }
1248 for (auto &poly : polys) {
1249 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
1250 continue;
1251 }
1252 for (int i = 0; i < 3; ++i) {
1253 poly.tlist[i] = poly.vlist[i] + pos;
1254 }
1255 }
1256 }
1257
1258 /// Append one triangle to the render list.
1259 void BuildRenderList(const Triangle &triangle) {
1260 polys.push_back(triangle);
1261 num_polys = static_cast<int>(polys.size());
1262 }
1263 };
1264
1265 /// Simple mesh object loaded from PLG-style indexed triangle data.
1266 class mxObject {
1267 public:
1268 /// Object state flags.
1269 int state = MX_ACTIVE;
1270
1271 /// Object attributes.
1272 int attr = 0;
1273
1274 /// Average radius from the local origin.
1275 float avg_rad = 0.0f;
1276
1277 /// Maximum radius from the local origin.
1278 float max_rad = 0.0f;
1279
1280 /// Object world position.
1281 vec4D world_pos;
1282
1283 /// Object direction.
1284 vec4D dir;
1285
1286 /// Local X basis vector.
1287 vec4D ux;
1288
1289 /// Local Y basis vector.
1290 vec4D uy;
1291
1292 /// Local Z basis vector.
1293 vec4D uz;
1294
1295 /// Number of loaded vertices.
1296 int num_vertices = 0;
1297
1298 /// Number of loaded polygons.
1299 int num_polys = 0;
1300
1301 /// Local-space vertices.
1302 std::vector<vec4D> local;
1303
1304 /// Transformed vertices.
1305 std::vector<vec4D> trans;
1306
1307 /// Optional texture coordinates corresponding to local-space vertices.
1308 std::vector<vec2D> texcoords;
1309
1310 /// Indexed triangle list.
1311 std::vector<Triangle> vlist;
1312
1313 /// Object name loaded from the model file.
1314 std::string object_name;
1315
1316 /// Materials loaded from the OBJ material library.
1317 std::vector<OBJMaterial> materials;
1318
1319 /// Resolved path of the OBJ material library.
1320 std::string material_library_path;
1321
1322 /**
1323 * @brief Load a PLG mesh file.
1324 * @param path File path to load.
1325 * @param scale Per-axis scale applied to vertices.
1326 * @param obj_pos World position assigned to the object.
1327 * @param rotation Object direction assigned after loading.
1328 * @return True when the file is opened and parsed successfully.
1329 *
1330 * Vertex lines may contain optional `u v` texture coordinates after `x y z`.
1331 * Blank lines and lines beginning with `#` are ignored. If loading fails, the
1332 * object retains its previous mesh, texture coordinates, and transform.
1333 */
1334 [[nodiscard]] bool LoadPLG(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation) {
1335 std::cout << "mxvk_math_eigen: loading PLG model: " << path << '\n';
1336 const auto load_failed = [&path](const char *reason) {
1337 std::cerr << "mxvk_math_eigen: failed to load PLG model '" << path << "': " << reason << '\n';
1338 return false;
1339 };
1340
1341 std::ifstream file(path);
1342 if (!file.is_open()) {
1343 return load_failed("could not open file");
1344 }
1345
1346 const auto read_data_line = [&file](std::string &line) {
1347 while (std::getline(file, line)) {
1348 const std::size_t comment = line.find('#');
1349 if (comment != std::string::npos) {
1350 line.erase(comment);
1351 }
1352 if (line.find_first_not_of(" \t\r\n") != std::string::npos) {
1353 return true;
1354 }
1355 }
1356 return false;
1357 };
1358
1359 std::string line;
1360 if (!read_data_line(line)) {
1361 return load_failed("missing model header");
1362 }
1363
1364 std::string name;
1365 int vertex_count = 0;
1366 int poly_count = 0;
1367 std::istringstream header(line);
1368 if (!(header >> name >> vertex_count >> poly_count) || vertex_count < 0 || poly_count < 0) {
1369 return load_failed("invalid model header");
1370 }
1371 std::cout << "mxvk_math_eigen: PLG header parsed (object='" << name << "', vertices=" << vertex_count << ", triangles=" << poly_count << ")\n";
1372
1373 std::vector<vec4D> loaded_local;
1374 std::vector<vec4D> loaded_trans;
1375 std::vector<vec2D> loaded_texcoords;
1376 std::vector<Triangle> loaded_vlist;
1377 loaded_local.reserve(static_cast<std::size_t>(vertex_count));
1378 loaded_trans.resize(static_cast<std::size_t>(vertex_count));
1379 loaded_texcoords.reserve(static_cast<std::size_t>(vertex_count));
1380 loaded_vlist.reserve(static_cast<std::size_t>(poly_count));
1381
1382 for (int i = 0; i < vertex_count; ++i) {
1383 if (!read_data_line(line)) {
1384 return load_failed("vertex data ended early");
1385 }
1386
1387 Eigen::Vector3f coordinates;
1388 std::istringstream vertex_line(line);
1389 if (!(vertex_line >> coordinates.x() >> coordinates.y() >> coordinates.z()) || !coordinates.allFinite()) {
1390 return load_failed("invalid vertex data");
1391 }
1392
1393 vec2D texcoord;
1394 if (vertex_line >> texcoord.x) {
1395 if (!(vertex_line >> texcoord.y) || !std::isfinite(texcoord.x) || !std::isfinite(texcoord.y)) {
1396 return load_failed("invalid texture coordinates");
1397 }
1398 }
1399
1400 const Eigen::Vector3f scaled = coordinates.cwiseProduct(Eigen::Vector3f(scale.x, scale.y, scale.z));
1401 if (!scaled.allFinite()) {
1402 return load_failed("scaled vertex is not finite");
1403 }
1404 loaded_local.emplace_back(scaled.x(), scaled.y(), scaled.z(), 1.0f);
1405 loaded_texcoords.push_back(texcoord);
1406 }
1407
1408 for (int i = 0; i < poly_count; ++i) {
1409 if (!read_data_line(line)) {
1410 return load_failed("triangle data ended early");
1411 }
1412
1413 Triangle tri;
1414 int count = 0;
1415 std::istringstream polygon_line(line);
1416 if (!(polygon_line >> std::hex >> tri.state >> std::dec >> count) || count != 3 || !(polygon_line >> tri.vert[0] >> tri.vert[1] >> tri.vert[2])) {
1417 return load_failed("invalid triangle data");
1418 }
1419 for (const int index : tri.vert) {
1420 if (index < 0 || index >= vertex_count) {
1421 return load_failed("triangle vertex index is out of range");
1422 }
1423 }
1424 loaded_vlist.push_back(tri);
1425 }
1426
1427 for (auto &triangle : loaded_vlist) {
1428 triangle.color = MXVK_RGB(rrand(0, 255), rrand(0, 255), rrand(0, 255));
1429 }
1430
1431 object_name = std::move(name);
1432 num_vertices = vertex_count;
1433 num_polys = poly_count;
1434 local = std::move(loaded_local);
1435 trans = std::move(loaded_trans);
1436 texcoords = std::move(loaded_texcoords);
1437 vlist = std::move(loaded_vlist);
1438 world_pos = obj_pos;
1439 dir = rotation;
1440 ComputeRad();
1441 std::cout << "mxvk_math_eigen: PLG model ready (object='" << object_name << "', average radius=" << avg_rad << ", maximum radius=" << max_rad << ")\n";
1442 return true;
1443 }
1444
1445 /// Load an MX mesh file using the PLG loader compatibility path.
1446 [[nodiscard]] bool LoadMX(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation) { return LoadPLG(path, scale, obj_pos, rotation); }
1447
1448 /**
1449 * @brief Load a Wavefront OBJ mesh and its referenced MTL material library.
1450 *
1451 * Wavefront faces may use independent position and texture-coordinate
1452 * indices, negative indices, triangles, quads, or concave polygons.
1453 * Faces are triangulated and de-indexed into the representation used by
1454 * the software rasterizer. MTL diffuse colors become triangle colors.
1455 */
1456 [[nodiscard]] bool LoadOBJ(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation) {
1457 std::cout << "mxvk_math_eigen: loading OBJ model: " << path << '\n';
1458 detail::OBJLoadResult loaded;
1459 std::string error;
1460 if (!detail::load_obj_file(path, loaded, error)) {
1461 std::cerr << "mxvk_math_eigen: failed to load OBJ model '" << path << "': " << error << '\n';
1462 return false;
1463 }
1464
1465 std::unordered_map<std::string, int> material_indices;
1466 for (std::size_t index = 0; index < loaded.materials.size(); ++index) {
1467 material_indices.emplace(loaded.materials[index].name, static_cast<int>(index));
1468 }
1469
1470 std::vector<vec4D> loaded_local;
1471 std::vector<vec2D> loaded_texcoords;
1472 std::vector<Triangle> loaded_vlist;
1473 loaded_local.reserve(loaded.triangles.size() * 3);
1474 loaded_texcoords.reserve(loaded.triangles.size() * 3);
1475 loaded_vlist.reserve(loaded.triangles.size());
1476
1477 for (const detail::OBJTriangle &source_triangle : loaded.triangles) {
1478 Triangle triangle;
1479 triangle.state = MX_ACTIVE;
1480 triangle.material_name = source_triangle.material_name;
1481 triangle.source_object_name = source_triangle.object_name;
1482 const auto material = material_indices.find(source_triangle.material_name);
1483 if (material != material_indices.end()) {
1484 triangle.material_index = material->second;
1485 triangle.color = material_color(loaded.materials[static_cast<std::size_t>(material->second)]);
1486 }
1487
1488 for (std::size_t vertex_index = 0; vertex_index < source_triangle.vertices.size(); ++vertex_index) {
1489 const detail::OBJVertex &source_vertex = source_triangle.vertices[vertex_index];
1490 const Eigen::Vector3f scaled = Eigen::Vector3f(source_vertex.position[0], source_vertex.position[1], source_vertex.position[2]).cwiseProduct(Eigen::Vector3f(scale.x, scale.y, scale.z));
1491 if (!scaled.allFinite()) {
1492 std::cerr << "mxvk_math_eigen: failed to load OBJ model '" << path << "': scaled vertex is not finite\n";
1493 return false;
1494 }
1495 triangle.vert[vertex_index] = static_cast<int>(loaded_local.size());
1496 loaded_local.emplace_back(scaled.x(), scaled.y(), scaled.z(), 1.0f);
1497 loaded_texcoords.emplace_back(source_vertex.texcoord[0], source_vertex.texcoord[1]);
1498 }
1499 loaded_vlist.push_back(triangle);
1500 }
1501
1502 object_name = std::move(loaded.object_name);
1503 num_vertices = static_cast<int>(loaded_local.size());
1504 num_polys = static_cast<int>(loaded_vlist.size());
1505 local = std::move(loaded_local);
1506 trans.resize(local.size());
1507 texcoords = std::move(loaded_texcoords);
1508 vlist = std::move(loaded_vlist);
1509 materials = std::move(loaded.materials);
1510 material_library_path = std::move(loaded.material_library_path);
1511 world_pos = obj_pos;
1512 dir = rotation;
1513 ComputeRad();
1514 std::cout << "mxvk_math_eigen: OBJ model ready (object='" << object_name << "', vertices=" << num_vertices << ", triangles=" << num_polys << ", materials=" << materials.size() << ")\n";
1515 return true;
1516 }
1517
1518 /**
1519 * @brief Replace this object's material library with an MTL file.
1520 * @return True when the material file is parsed successfully.
1521 */
1522 [[nodiscard]] bool LoadMTL(const std::string &path) {
1523 std::vector<OBJMaterial> loaded_materials;
1524 std::string error;
1525 if (!detail::load_mtl_file(path, loaded_materials, error)) {
1526 std::cerr << "mxvk_math_eigen: failed to load MTL file '" << path << "': " << error << '\n';
1527 return false;
1528 }
1529
1530 std::unordered_map<std::string, int> material_indices;
1531 for (std::size_t index = 0; index < loaded_materials.size(); ++index) {
1532 material_indices.emplace(loaded_materials[index].name, static_cast<int>(index));
1533 }
1534 for (std::size_t index = 0; index < vlist.size(); ++index) {
1535 const auto material = material_indices.find(vlist[index].material_name);
1536 vlist[index].material_index = material == material_indices.end() ? -1 : material->second;
1537 if (material != material_indices.end()) {
1538 vlist[index].color = material_color(loaded_materials[static_cast<std::size_t>(material->second)]);
1539 }
1540 }
1541
1542 materials = std::move(loaded_materials);
1543 material_library_path = path;
1544 return true;
1545 }
1546
1547 /// Convert local or transformed vertices to world space.
1548 void ModelToWorld(int type = 0) {
1549 if (type == 0) {
1550 trans.resize(local.size());
1551 for (std::size_t i = 0; i < local.size(); ++i) {
1552 trans[i] = local[i] + world_pos;
1553 }
1554 } else if (type == 1) {
1555 for (auto &vertex : trans) {
1556 vertex += world_pos;
1557 }
1558 }
1559 }
1560
1561 /// Apply a transform to local vertices, transformed vertices, or local-to-transformed output.
1562 void TransformObject(const Mat4D &mrot, int type = 0) {
1563 auto transform = [&mrot](std::vector<vec4D> &vertices) {
1564 for (auto &vertex : vertices) {
1565 vertex = mrot.MulVec(vertex);
1566 }
1567 };
1568 if (type == 0) {
1569 transform(local);
1570 } else if (type == 1) {
1571 transform(trans);
1572 } else if (type == 2) {
1573 trans.resize(local.size());
1574 for (std::size_t i = 0; i < local.size(); ++i) {
1575 trans[i] = mrot.MulVec(local[i]);
1576 }
1577 }
1578 }
1579
1580 /// Mark object polygons that face away from a view position.
1581 void RemoveFaces(const vec4D &pos) {
1582 for (auto &poly : vlist) {
1583 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0 || (poly.state & MX_CULLED) != 0) {
1584 continue;
1585 }
1586 if (poly.vert[0] < 0 || poly.vert[1] < 0 || poly.vert[2] < 0 || static_cast<std::size_t>(poly.vert[0]) >= trans.size() || static_cast<std::size_t>(poly.vert[1]) >= trans.size() || static_cast<std::size_t>(poly.vert[2]) >= trans.size()) {
1587 continue;
1588 }
1589 const vec4D u = vec4D().Build(trans[static_cast<std::size_t>(poly.vert[0])], trans[static_cast<std::size_t>(poly.vert[1])]);
1590 const vec4D v = vec4D().Build(trans[static_cast<std::size_t>(poly.vert[0])], trans[static_cast<std::size_t>(poly.vert[2])]);
1591 const vec4D n = u.CrossProduct(v);
1592 const vec4D view = vec4D().Build(trans[static_cast<std::size_t>(poly.vert[0])], pos);
1593 if (n.DotProduct(view) <= 0.0f) {
1594 poly.state |= MX_BACKFACE;
1595 }
1596 }
1597 }
1598
1599 /// Append this object's triangles to a render list.
1600 void BuildRenderList(RenderList &list) const {
1601 for (const auto &poly : vlist) {
1602 Triangle tri = poly;
1603 for (int i = 0; i < 3; ++i) {
1604 const auto index = static_cast<std::size_t>(poly.vert[i]);
1605 if (index < local.size()) {
1606 tri.vlist[i] = local[index];
1607 }
1608 if (index < trans.size()) {
1609 tri.tlist[i] = trans[index];
1610 }
1611 }
1612 list.BuildRenderList(tri);
1613 }
1614 }
1615
1616 /// Reset object and polygon state to active.
1617 void Reset() {
1618 for (auto &poly : vlist) {
1619 poly.state = MX_ACTIVE;
1620 }
1621 state = MX_ACTIVE;
1622 }
1623
1624 /// Compute and cache average and maximum local-space radii.
1625 float ComputeRad() {
1626 max_rad = 0.0f;
1627 avg_rad = 0.0f;
1628 if (local.empty()) {
1629 return 0.0f;
1630 }
1631 for (const auto &vertex : local) {
1632 const float dist = Eigen::Vector3f(vertex.x, vertex.y, vertex.z).norm();
1633 avg_rad += dist;
1634 max_rad = std::max(max_rad, dist);
1635 }
1636 avg_rad /= static_cast<float>(local.size());
1637 return max_rad;
1638 }
1639
1640 /// Replace the object state flags.
1641 void SetState(int new_state) { state = new_state; }
1642
1643 private:
1644 [[nodiscard]] static MXCOLOR material_color(const OBJMaterial &material) {
1645 const auto channel = [](float value) { return static_cast<MXCOLOR>(std::lround(std::clamp(value, 0.0f, 1.0f) * 255.0f)); };
1646 return (channel(material.dissolve) << 24u) | (channel(material.diffuse[0]) << 16u) | (channel(material.diffuse[1]) << 8u) | channel(material.diffuse[2]);
1647 }
1648 };
1649
1650 /// Camera and projection data for the simple software 3D pipeline.
1651 class Camera {
1652 public:
1653 /// Camera state flags.
1654 int state = 0;
1655
1656 /// Camera attributes.
1657 int attr = 0;
1658
1659 /// Camera position.
1660 vec4D pos;
1661
1662 /// Euler camera direction in degrees.
1663 vec4D dir;
1664
1665 /// UVN camera U basis vector.
1666 vec4D u;
1667
1668 /// UVN camera V basis vector.
1669 vec4D v;
1670
1671 /// UVN camera N basis vector.
1672 vec4D n;
1673
1674 /// Camera look-at target.
1675 vec4D target;
1676
1677 /// Distance from camera to view plane.
1678 float view_dist = 1.0f;
1679
1680 /// Vertical field of view in degrees.
1681 float fov = 90.0f;
1682
1683 /// Near clipping plane Z.
1684 float near_clip_z = 1.0f;
1685
1686 /// Far clipping plane Z.
1687 float far_clip_z = 1000.0f;
1688
1689 /// Right clipping plane.
1690 Plane3D rt_clip_plane;
1691
1692 /// Left clipping plane.
1693 Plane3D lt_clip_plane;
1694
1695 /// Top clipping plane.
1696 Plane3D tp_clip_plane;
1697
1698 /// Bottom clipping plane.
1699 Plane3D bt_clip_plane;
1700
1701 /// View-plane height.
1702 float viewplane_height = 2.0f;
1703
1704 /// View-plane width.
1705 float viewplane_width = 2.0f;
1706
1707 /// Viewport width in pixels.
1708 float viewport_width = 1.0f;
1709
1710 /// Viewport height in pixels.
1711 float viewport_height = 1.0f;
1712
1713 /// Viewport center X coordinate.
1714 float viewport_center_x = 0.0f;
1715
1716 /// Viewport center Y coordinate.
1717 float viewport_center_y = 0.0f;
1718
1719 /// Viewport aspect ratio.
1720 float aspect_ratio = 1.0f;
1721
1722 /// World-to-camera transform matrix.
1723 Mat4D mcam;
1724
1725 /// Perspective transform matrix placeholder.
1726 Mat4D mper;
1727
1728 /// Screen transform matrix placeholder.
1729 Mat4D mscr;
1730
1731 /// Construct a camera with identity matrices.
1733 mcam.LoadIdentity();
1734 mper.LoadIdentity();
1735 mscr.LoadIdentity();
1736 }
1737
1738 /// Initialize camera fields for the Euler example path.
1740 pos.Set(100.0f, 200.0f, 300.0f);
1741 dir.Set(-48.0f, 0.0f, 0.0f);
1742 BuildEuler(5);
1743 }
1744
1745 /// Initialize camera projection, viewport, position, and direction parameters.
1746 void Init(int camera_attr, const vec4D &camera_pos, const vec4D &camera_dir, const vec4D *camera_target, float near_z, float far_z, float fov_degrees, float width, float height) {
1747 attr = camera_attr;
1748 pos = camera_pos;
1749 dir = camera_dir;
1750 target = camera_target != nullptr ? *camera_target : vec4D();
1751 near_clip_z = near_z;
1752 far_clip_z = far_z;
1753 fov = fov_degrees;
1754 viewport_width = std::max(1.0f, width);
1755 viewport_height = std::max(1.0f, height);
1756 viewport_center_x = (viewport_width - 1.0f) * 0.5f;
1757 viewport_center_y = (viewport_height - 1.0f) * 0.5f;
1759 viewplane_width = 2.0f;
1761 view_dist = (viewplane_width * 0.5f) / std::tan(deg2rad(fov * 0.5f));
1762 mcam.LoadIdentity();
1763 mper.LoadIdentity();
1764 mscr.LoadIdentity();
1765 }
1766
1767 /// Build the world-to-camera matrix from Euler direction angles.
1768 void BuildEuler(int) {
1769 Mat4D translation(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -pos.x, -pos.y, -pos.z, 1);
1770 Mat4D rotation;
1771 rotation.BuildXYZ(-dir.x, -dir.y, -dir.z);
1772 mcam = translation * rotation;
1773 }
1774
1775 /// Build the world-to-camera matrix from the camera position and look-at target.
1776 void BuildUVN(int) {
1777 n = vec4D().Build(pos, target);
1778 n.Normalize();
1779 v.Set(0.0f, 1.0f, 0.0f);
1780 u = v.CrossProduct(n);
1781 u.Normalize();
1782 v = n.CrossProduct(u);
1783 v.Normalize();
1784 Mat4D translation(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -pos.x, -pos.y, -pos.z, 1);
1785 Mat4D uvn(u.x, v.x, n.x, 0, u.y, v.y, n.y, 0, u.z, v.z, n.z, 0, 0, 0, 0, 1);
1786 mcam = translation * uvn;
1787 }
1788
1789 /// Transform a render list from world space to camera space.
1790 void WorldToCamera(RenderList &list) const {
1791 for (auto &poly : list.polys) {
1792 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
1793 continue;
1794 }
1795 for (auto &vertex : poly.tlist) {
1796 vertex = mcam.MulVec(vertex);
1797 }
1798 }
1799 }
1800
1801 /// Transform an object's transformed vertices from world space to camera space.
1802 void WorldToCamera(mxObject &object) const {
1803 for (auto &vertex : object.trans) {
1804 vertex = mcam.MulVec(vertex);
1805 }
1806 }
1807
1808 /// Project a render list from camera space to perspective space.
1810 for (auto &poly : list.polys) {
1811 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
1812 continue;
1813 }
1814 for (auto &vertex : poly.tlist) {
1815 if (std::fabs(vertex.z) <= EPSILON) {
1816 continue;
1817 }
1818 const Eigen::Vector2f projected = Eigen::Vector2f(vertex.x, vertex.y).cwiseProduct(Eigen::Vector2f(view_dist, view_dist * aspect_ratio)) / vertex.z;
1819 vertex.x = projected.x();
1820 vertex.y = projected.y();
1821 }
1822 }
1823 }
1824
1825 /// Project an object's transformed vertices from camera space to perspective space.
1826 void CameraToPerspective(mxObject &object) const {
1827 for (auto &vertex : object.trans) {
1828 if (std::fabs(vertex.z) <= EPSILON) {
1829 continue;
1830 }
1831 const Eigen::Vector2f projected = Eigen::Vector2f(vertex.x, vertex.y).cwiseProduct(Eigen::Vector2f(view_dist, view_dist * aspect_ratio)) / vertex.z;
1832 vertex.x = projected.x();
1833 vertex.y = projected.y();
1834 }
1835 }
1836
1837 /// Convert a render list from perspective coordinates to screen coordinates.
1839 const float alpha = viewport_center_x;
1840 const float beta = viewport_center_y;
1841 for (auto &poly : list.polys) {
1842 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
1843 continue;
1844 }
1845 for (auto &vertex : poly.tlist) {
1846 const Eigen::Vector2f screen = Eigen::Vector2f(alpha, beta) + Eigen::Vector2f(alpha, -beta).cwiseProduct(Eigen::Vector2f(vertex.x, vertex.y));
1847 vertex.x = screen.x();
1848 vertex.y = screen.y();
1849 }
1850 }
1851 }
1852
1853 /// Convert an object's transformed vertices from perspective coordinates to screen coordinates.
1854 void PerspectiveToScreen(mxObject &object) const {
1855 const float alpha = viewport_center_x;
1856 const float beta = viewport_center_y;
1857 for (auto &vertex : object.trans) {
1858 const Eigen::Vector2f screen = Eigen::Vector2f(alpha, beta) + Eigen::Vector2f(alpha, -beta).cwiseProduct(Eigen::Vector2f(vertex.x, vertex.y));
1859 vertex.x = screen.x();
1860 vertex.y = screen.y();
1861 }
1862 }
1863 };
1864
1865 /// Pixel plotter adapter that writes packed MXVK colors to an SDL renderer.
1866 struct SDLRendererPixelPlotter {
1867 /// SDL renderer receiving plotted pixels.
1868 SDL_Renderer *renderer = nullptr;
1869
1870 /// Plot one pixel if the renderer is valid.
1871 void operator()(int x, int y, MXCOLOR color) const {
1872 if (renderer == nullptr) {
1873 return;
1874 }
1875 SDL_SetRenderDrawColor(renderer, color_r(color), color_g(color), color_b(color), color_a(color));
1876 SDL_RenderPoint(renderer, static_cast<float>(x), static_cast<float>(y));
1877 }
1878 };
1879
1880 /// Pixel plotter adapter that draws square pixels into a VK_Sprite.
1881 struct VKSpritePixelPlotter {
1882 /// Sprite receiving plotted pixels.
1883 VK_Sprite *sprite = nullptr;
1884
1885 /// Square pixel size in sprite coordinates.
1886 int size = 1;
1887
1888 /// Plot one square pixel if the sprite is valid.
1889 void operator()(int x, int y, MXCOLOR) const {
1890 if (sprite != nullptr) {
1891 sprite->drawSpriteRect(x, y, std::max(1, size), std::max(1, size));
1892 }
1893 }
1894 };
1895
1896 /**
1897 * @brief Draw a line with Bresenham-style integer stepping.
1898 * @tparam PlotPixel Callable accepting (x, y, MXCOLOR).
1899 * @param x0 Start X coordinate.
1900 * @param y0 Start Y coordinate.
1901 * @param x1 End X coordinate.
1902 * @param y1 End Y coordinate.
1903 * @param color Packed ARGB color.
1904 * @param plot_pixel Pixel plotting callable.
1905 */
1906 template <typename PlotPixel> void draw_line(int x0, int y0, int x1, int y1, MXCOLOR color, PlotPixel &&plot_pixel) {
1907 const int dx = std::abs(x1 - x0);
1908 const int sx = x0 < x1 ? 1 : -1;
1909 const int dy = -std::abs(y1 - y0);
1910 const int sy = y0 < y1 ? 1 : -1;
1911 int err = dx + dy;
1912
1913 while (1) {
1914 plot_pixel(x0, y0, color);
1915 if (x0 == x1 && y0 == y1) {
1916 break;
1917 }
1918 const int e2 = 2 * err;
1919 if (e2 >= dy) {
1920 err += dy;
1921 x0 += sx;
1922 }
1923 if (e2 <= dx) {
1924 err += dx;
1925 y0 += sy;
1926 }
1927 }
1928 }
1929
1930 /// Draw a line directly to an SDL renderer.
1931 inline void draw_line(SDL_Renderer *renderer, int x0, int y0, int x1, int y1, MXCOLOR color) { draw_line(x0, y0, x1, y1, color, SDLRendererPixelPlotter{renderer}); }
1932
1933 /// Draw a line directly to a VK_Sprite with optional square pixel size.
1934 inline void draw_line(VK_Sprite &sprite, int x0, int y0, int x1, int y1, MXCOLOR color, int pixel_size = 1) { draw_line(x0, y0, x1, y1, color, VKSpritePixelPlotter{&sprite, pixel_size}); }
1935
1936 /// Compute the signed edge function value for point @p p relative to edge @p a-@p b.
1937 [[nodiscard]] inline float edge_function(const vec2D &a, const vec2D &b, const vec2D &p) {
1938 Eigen::Matrix2f edges;
1939 edges << p.x - a.x, b.x - a.x, p.y - a.y, b.y - a.y;
1940 return edges.determinant();
1941 }
1942
1943 /**
1944 * @brief Rasterize a filled triangle by testing pixels against edge functions.
1945 * @tparam PlotPixel Callable accepting (x, y, MXCOLOR).
1946 * @param p0 First triangle vertex in screen coordinates.
1947 * @param p1 Second triangle vertex in screen coordinates.
1948 * @param p2 Third triangle vertex in screen coordinates.
1949 * @param color Packed ARGB color.
1950 * @param plot_pixel Pixel plotting callable.
1951 */
1952 template <typename PlotPixel> void draw_filled_triangle(const vec2D &p0, const vec2D &p1, const vec2D &p2, MXCOLOR color, PlotPixel &&plot_pixel) {
1953 const float area = edge_function(p0, p1, p2);
1954 if (std::fabs(area) <= EPSILON) {
1955 return;
1956 }
1957
1958 const int min_x = static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x})));
1959 const int max_x = static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x})));
1960 const int min_y = static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y})));
1961 const int max_y = static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y})));
1962
1963 for (int y = min_y; y <= max_y; ++y) {
1964 for (int x = min_x; x <= max_x; ++x) {
1965 const vec2D p(static_cast<float>(x) + 0.5f, static_cast<float>(y) + 0.5f);
1966 const float w0 = edge_function(p1, p2, p);
1967 const float w1 = edge_function(p2, p0, p);
1968 const float w2 = edge_function(p0, p1, p);
1969 if ((area > 0.0f && w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) || (area < 0.0f && w0 <= 0.0f && w1 <= 0.0f && w2 <= 0.0f)) {
1970 plot_pixel(x, y, color);
1971 }
1972 }
1973 }
1974 }
1975
1976 /// Draw a filled triangle directly to an SDL renderer.
1977 inline void draw_filled_triangle(SDL_Renderer *renderer, const vec2D &p0, const vec2D &p1, const vec2D &p2, MXCOLOR color) { draw_filled_triangle(p0, p1, p2, color, SDLRendererPixelPlotter{renderer}); }
1978
1979 /// Draw a filled triangle directly to a VK_Sprite with optional square pixel size.
1980 inline void draw_filled_triangle(VK_Sprite &sprite, const vec2D &p0, const vec2D &p1, const vec2D &p2, MXCOLOR color, int pixel_size = 1) { draw_filled_triangle(p0, p1, p2, color, VKSpritePixelPlotter{&sprite, pixel_size}); }
1981
1982 /**
1983 * @brief Rasterize a filled triangle as horizontal spans.
1984 * @tparam DrawSpan Callable accepting (x0, x1, y, MXCOLOR).
1985 * @param p0 First triangle vertex in screen coordinates.
1986 * @param p1 Second triangle vertex in screen coordinates.
1987 * @param p2 Third triangle vertex in screen coordinates.
1988 * @param clip_min_y Minimum inclusive scanline to rasterize.
1989 * @param clip_max_y Maximum inclusive scanline to rasterize.
1990 * @param color Packed ARGB color.
1991 * @param draw_span Span drawing callable.
1992 */
1993 template <typename DrawSpan> void draw_filled_triangle_spans_clipped(vec2D p0, vec2D p1, vec2D p2, int clip_min_y, int clip_max_y, MXCOLOR color, DrawSpan &&draw_span) {
1994 if (std::fabs(edge_function(p0, p1, p2)) <= EPSILON) {
1995 return;
1996 }
1997
1998 struct SpanEdge {
1999 int y0 = 0;
2000 int y1 = -1;
2001 float x0 = 0.0f;
2002 float slope = 0.0f;
2003
2004 [[nodiscard]] float x_at(int y) const { return x0 + static_cast<float>(y - y0) * slope; }
2005 };
2006
2007 const int min_y = std::max(clip_min_y, static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y}))));
2008 const int max_y = std::min(clip_max_y, static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y}))));
2009 if (min_y > max_y) {
2010 return;
2011 }
2012
2013 std::array<SpanEdge, 3> edges{};
2014 int edge_count = 0;
2015 const auto add_edge = [&](vec2D a, vec2D b) {
2016 if (std::fabs(a.y - b.y) <= EPSILON) {
2017 return;
2018 }
2019 if (a.y > b.y) {
2020 std::swap(a, b);
2021 }
2022
2023 const int y0 = std::max(min_y, static_cast<int>(std::ceil(a.y - 0.5f)));
2024 const int y1 = std::min(max_y, static_cast<int>(std::ceil(b.y - 0.5f)) - 1);
2025 if (y0 > y1) {
2026 return;
2027 }
2028
2029 SpanEdge &edge = edges[static_cast<std::size_t>(edge_count++)];
2030 edge.y0 = y0;
2031 edge.y1 = y1;
2032 edge.slope = (b.x - a.x) / (b.y - a.y);
2033 edge.x0 = a.x + ((static_cast<float>(y0) + 0.5f) - a.y) * edge.slope;
2034 };
2035
2036 add_edge(p0, p1);
2037 add_edge(p1, p2);
2038 add_edge(p2, p0);
2039
2040 for (int y = min_y; y <= max_y; ++y) {
2041 std::array<float, 3> intersections{};
2042 int intersection_count = 0;
2043
2044 for (int edge_index = 0; edge_index < edge_count; ++edge_index) {
2045 const SpanEdge &edge = edges[static_cast<std::size_t>(edge_index)];
2046 if (y >= edge.y0 && y <= edge.y1) {
2047 intersections[static_cast<std::size_t>(intersection_count++)] = edge.x_at(y);
2048 }
2049 }
2050
2051 if (intersection_count < 2) {
2052 continue;
2053 }
2054
2055 float min_x = intersections[0];
2056 float max_x = intersections[0];
2057 for (int i = 1; i < intersection_count; ++i) {
2058 min_x = std::min(min_x, intersections[static_cast<std::size_t>(i)]);
2059 max_x = std::max(max_x, intersections[static_cast<std::size_t>(i)]);
2060 }
2061
2062 const int x0 = static_cast<int>(std::ceil(min_x - 0.5f));
2063 const int x1 = static_cast<int>(std::floor(max_x - 0.5f));
2064 if (x0 <= x1) {
2065 draw_span(x0, x1, y, color);
2066 }
2067 }
2068 }
2069
2070 template <typename DrawSpan> void draw_filled_triangle_spans(vec2D p0, vec2D p1, vec2D p2, MXCOLOR color, DrawSpan &&draw_span) { draw_filled_triangle_spans_clipped(p0, p1, p2, std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), color, std::forward<DrawSpan>(draw_span)); }
2071
2072 /// Clipped software rasterization pipeline for lines and filled triangles.
2073 class PipeLine {
2074 public:
2075 /// Cohen-Sutherland region codes for line clipping.
2077 /// Center/inside code.
2078 CODE_C = 0x0000,
2079
2080 /// North/top code.
2081 CODE_N = 0x0008,
2082
2083 /// South/bottom code.
2084 CODE_S = 0x0004,
2085
2086 /// East/right code.
2087 CODE_E = 0x0002,
2088
2089 /// West/left code.
2090 CODE_W = 0x0001
2091 };
2092
2093 /// Maximum clip X coordinate.
2094 int max_clip_x = 0;
2095
2096 /// Maximum clip Y coordinate.
2097 int max_clip_y = 0;
2098
2099 /// Minimum clip X coordinate.
2100 int min_clip_x = 0;
2101
2102 /// Minimum clip Y coordinate.
2103 int min_clip_y = 0;
2104
2105 /// Active minimum clip X coordinate.
2106 int clip_min_x = 0;
2107
2108 /// Active maximum clip X coordinate.
2109 int clip_max_x = 0;
2110
2111 /// Active minimum clip Y coordinate.
2112 int clip_min_y = 0;
2113
2114 /// Active maximum clip Y coordinate.
2115 int clip_max_y = 0;
2116
2117 /// Begin rendering with a custom pixel plotter.
2118 void Begin(int width, int height, std::function<void(int, int, MXCOLOR)> plotter) {
2119 plot_pixel = std::move(plotter);
2120 plot_span = [this](int x0, int x1, int y, MXCOLOR color) {
2121 for (int x = x0; x <= x1; ++x) {
2122 plot_pixel(x, y, color);
2123 }
2124 };
2125 clip_min_x = min_clip_x = 0;
2126 clip_min_y = min_clip_y = 0;
2127 clip_max_x = max_clip_x = std::max(0, width - 1);
2128 clip_max_y = max_clip_y = std::max(0, height - 1);
2129 [[maybe_unused]] static const bool PIPELINE_LOGGED = [width, height] {
2130 std::cout << "mxvk_math_eigen: software raster pipeline ready (" << width << 'x' << height << ")\n";
2131 return true;
2132 }();
2133 }
2134
2135 /// Begin rendering to an SDL renderer.
2136 void Begin(SDL_Renderer *renderer, int width, int height) {
2137 Begin(width, height, [renderer](int x, int y, MXCOLOR color) { SDLRendererPixelPlotter{renderer}(x, y, color); });
2138 plot_span = [renderer](int x0, int x1, int y, MXCOLOR color) {
2139 if (renderer == nullptr) {
2140 return;
2141 }
2142 SDL_SetRenderDrawColor(renderer, color_r(color), color_g(color), color_b(color), color_a(color));
2143 SDL_RenderLine(renderer, static_cast<float>(x0), static_cast<float>(y), static_cast<float>(x1), static_cast<float>(y));
2144 };
2145 }
2146
2147 /// Begin rendering to a VK_Sprite with optional square pixel size.
2148 void Begin(VK_Sprite &sprite, int width, int height, int pixel_size = 1) {
2149 Begin(width, height, [&sprite, pixel_size](int x, int y, MXCOLOR color) { VKSpritePixelPlotter{&sprite, pixel_size}(x, y, color); });
2150 plot_span = [&sprite, pixel_size](int x0, int x1, int y, MXCOLOR) {
2151 const int size = std::max(1, pixel_size);
2152 sprite.drawSpriteRect(x0, y, std::max(1, x1 - x0 + 1), size);
2153 };
2154 }
2155
2156 /// Compute the Cohen-Sutherland region code for a point.
2157 [[nodiscard]] int ComputeCode(int x, int y) const {
2158 int code = CODE_C;
2159 if (x < clip_min_x) {
2160 code |= CODE_W;
2161 } else if (x > clip_max_x) {
2162 code |= CODE_E;
2163 }
2164 if (y < clip_min_y) {
2165 code |= CODE_N;
2166 } else if (y > clip_max_y) {
2167 code |= CODE_S;
2168 }
2169 return code;
2170 }
2171
2172 /**
2173 * @brief Clip a line segment to the active clip rectangle.
2174 * @param x0 Start X coordinate, updated to clipped value.
2175 * @param y0 Start Y coordinate, updated to clipped value.
2176 * @param x1 End X coordinate, updated to clipped value.
2177 * @param y1 End Y coordinate, updated to clipped value.
2178 * @return True when some portion of the line remains visible.
2179 */
2180 bool ClipLine(int &x0, int &y0, int &x1, int &y1) const {
2181 int code0 = ComputeCode(x0, y0);
2182 int code1 = ComputeCode(x1, y1);
2183 const auto interpolate = [](int dependent0, int dependent1, int independent0, int independent1, int boundary) {
2184 const double value = static_cast<double>(dependent0) + (static_cast<double>(dependent1) - static_cast<double>(dependent0)) * (static_cast<double>(boundary) - static_cast<double>(independent0)) / (static_cast<double>(independent1) - static_cast<double>(independent0));
2185 return static_cast<int>(std::lround(value));
2186 };
2187
2188 while (true) {
2189 if ((code0 | code1) == 0) {
2190 return true;
2191 }
2192 if ((code0 & code1) != 0) {
2193 return false;
2194 }
2195
2196 const int out_code = code0 != 0 ? code0 : code1;
2197 int x = 0;
2198 int y = 0;
2199
2200 if ((out_code & CODE_N) != 0) {
2201 if (y1 == y0) {
2202 return false;
2203 }
2204 x = interpolate(x0, x1, y0, y1, clip_min_y);
2205 y = clip_min_y;
2206 } else if ((out_code & CODE_S) != 0) {
2207 if (y1 == y0) {
2208 return false;
2209 }
2210 x = interpolate(x0, x1, y0, y1, clip_max_y);
2211 y = clip_max_y;
2212 } else if ((out_code & CODE_E) != 0) {
2213 if (x1 == x0) {
2214 return false;
2215 }
2216 y = interpolate(y0, y1, x0, x1, clip_max_x);
2217 x = clip_max_x;
2218 } else {
2219 if (x1 == x0) {
2220 return false;
2221 }
2222 y = interpolate(y0, y1, x0, x1, clip_min_x);
2223 x = clip_min_x;
2224 }
2225
2226 if (out_code == code0) {
2227 x0 = x;
2228 y0 = y;
2229 code0 = ComputeCode(x0, y0);
2230 } else {
2231 x1 = x;
2232 y1 = y;
2233 code1 = ComputeCode(x1, y1);
2234 }
2235 }
2236 }
2237
2238 /// Draw a clipped line. Kept with the original misspelled name for compatibility.
2239 void DrawClipedLine(int x0, int y0, int x1, int y1, MXCOLOR color) const {
2240 if (plot_pixel && ClipLine(x0, y0, x1, y1)) {
2241 draw_line(x0, y0, x1, y1, color, plot_pixel);
2242 }
2243 }
2244
2245 /// Draw a clipped line.
2246 void DrawClippedLine(int x0, int y0, int x1, int y1, MXCOLOR color) const { DrawClipedLine(x0, y0, x1, y1, color); }
2247
2248 /**
2249 * @brief Draw a clipped screen-space line with perspective-correct depth testing.
2250 * @param first First endpoint; x/y are screen coordinates and z is positive view depth.
2251 * @param second Second endpoint; x/y are screen coordinates and z is positive view depth.
2252 * @param color Packed line color.
2253 * @param depth_buffer Full framebuffer depth buffer initialized to a far value.
2254 */
2255 void DrawDepthTestedLine(const vec4D &first, const vec4D &second, MXCOLOR color, std::span<float> depth_buffer) const {
2256 if (!plot_pixel || first.z <= EPSILON || second.z <= EPSILON || !std::isfinite(first.x) || !std::isfinite(first.y) || !std::isfinite(first.z) || !std::isfinite(second.x) || !std::isfinite(second.y) || !std::isfinite(second.z)) {
2257 return;
2258 }
2259
2260 const int framebuffer_width = max_clip_x + 1;
2261 const int framebuffer_height = max_clip_y + 1;
2262 const std::size_t required_depth_values = static_cast<std::size_t>(framebuffer_width) * static_cast<std::size_t>(framebuffer_height);
2263 if (framebuffer_width <= 0 || framebuffer_height <= 0 || depth_buffer.size() < required_depth_values) {
2264 return;
2265 }
2266
2267 const float delta_x = second.x - first.x;
2268 const float delta_y = second.y - first.y;
2269 float first_fraction = 0.0f;
2270 float last_fraction = 1.0f;
2271 const auto clip_fraction = [&first_fraction, &last_fraction](float direction, float distance) {
2272 if (std::abs(direction) <= EPSILON) {
2273 return distance >= 0.0f;
2274 }
2275
2276 const float fraction = distance / direction;
2277 if (direction < 0.0f) {
2278 first_fraction = std::max(first_fraction, fraction);
2279 } else {
2280 last_fraction = std::min(last_fraction, fraction);
2281 }
2282 return first_fraction <= last_fraction;
2283 };
2284
2285 if (!clip_fraction(-delta_x, first.x - static_cast<float>(clip_min_x)) || !clip_fraction(delta_x, static_cast<float>(clip_max_x) - first.x) || !clip_fraction(-delta_y, first.y - static_cast<float>(clip_min_y)) || !clip_fraction(delta_y, static_cast<float>(clip_max_y) - first.y)) {
2286 return;
2287 }
2288
2289 const float clipped_delta_x = delta_x * (last_fraction - first_fraction);
2290 const float clipped_delta_y = delta_y * (last_fraction - first_fraction);
2291 const float step_count = std::ceil(std::max(std::abs(clipped_delta_x), std::abs(clipped_delta_y)));
2292 if (step_count > static_cast<float>(std::numeric_limits<int>::max())) {
2293 return;
2294 }
2295 const int steps = std::max(1, static_cast<int>(step_count));
2296 const float first_reciprocal_depth = 1.0f / first.z;
2297 const float second_reciprocal_depth = 1.0f / second.z;
2298
2299 for (int step = 0; step <= steps; ++step) {
2300 const float clipped_fraction = static_cast<float>(step) / static_cast<float>(steps);
2301 const float fraction = first_fraction + (last_fraction - first_fraction) * clipped_fraction;
2302 const int x = static_cast<int>(std::lround(first.x + delta_x * fraction));
2303 const int y = static_cast<int>(std::lround(first.y + delta_y * fraction));
2304 if (x < clip_min_x || x > clip_max_x || y < clip_min_y || y > clip_max_y) {
2305 continue;
2306 }
2307
2308 const float reciprocal_depth = first_reciprocal_depth + (second_reciprocal_depth - first_reciprocal_depth) * fraction;
2309 if (reciprocal_depth <= EPSILON) {
2310 continue;
2311 }
2312
2313 const float depth = 1.0f / reciprocal_depth;
2314 const std::size_t pixel_index = static_cast<std::size_t>(y) * static_cast<std::size_t>(framebuffer_width) + static_cast<std::size_t>(x);
2315 if (depth >= depth_buffer[pixel_index]) {
2316 continue;
2317 }
2318
2319 depth_buffer[pixel_index] = depth;
2320 plot_pixel(x, y, color);
2321 }
2322 }
2323
2324 /// Draw a clipped wireframe triangle without depth testing.
2325 void DrawWireframeTriangle(const vec4D &first, const vec4D &second, const vec4D &third, MXCOLOR color) const {
2326 DrawClippedLine(static_cast<int>(std::lround(first.x)), static_cast<int>(std::lround(first.y)), static_cast<int>(std::lround(second.x)), static_cast<int>(std::lround(second.y)), color);
2327 DrawClippedLine(static_cast<int>(std::lround(second.x)), static_cast<int>(std::lround(second.y)), static_cast<int>(std::lround(third.x)), static_cast<int>(std::lround(third.y)), color);
2328 DrawClippedLine(static_cast<int>(std::lround(third.x)), static_cast<int>(std::lround(third.y)), static_cast<int>(std::lround(first.x)), static_cast<int>(std::lround(first.y)), color);
2329 }
2330
2331 /// Draw a clipped wireframe triangle with perspective-correct depth testing.
2332 void DrawWireframeTriangle(const vec4D &first, const vec4D &second, const vec4D &third, MXCOLOR color, std::span<float> depth_buffer) const {
2333 DrawDepthTestedLine(first, second, color, depth_buffer);
2334 DrawDepthTestedLine(second, third, color, depth_buffer);
2335 DrawDepthTestedLine(third, first, color, depth_buffer);
2336 }
2337
2338 /// Draw a clipped filled triangle from 2D screen-space vertices.
2339 void DrawFilledTriangle(const vec2D &p0, const vec2D &p1, const vec2D &p2, MXCOLOR color) const {
2340 if (!plot_pixel) {
2341 return;
2342 }
2343
2344 const int min_x = static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x})));
2345 const int max_x = static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x})));
2346 const int min_y = static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y})));
2347 const int max_y = static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y})));
2348 if (max_x < clip_min_x || min_x > clip_max_x || max_y < clip_min_y || min_y > clip_max_y) {
2349 return;
2350 }
2351
2352 draw_filled_triangle_spans_clipped(p0, p1, p2, clip_min_y, clip_max_y, color, [this](int x0, int x1, int y, MXCOLOR pixel_color) {
2353 if (x1 < clip_min_x || x0 > clip_max_x) {
2354 return;
2355 }
2356 x0 = std::max(x0, clip_min_x);
2357 x1 = std::min(x1, clip_max_x);
2358 if (plot_span) {
2359 plot_span(x0, x1, y, pixel_color);
2360 }
2361 });
2362 }
2363
2364 /// Draw a clipped filled triangle from homogeneous screen-space vertices.
2365 void DrawFilledTriangle(const vec4D &p0, const vec4D &p1, const vec4D &p2, MXCOLOR color) const { DrawFilledTriangle(vec2D(p0.x, p0.y), vec2D(p1.x, p1.y), vec2D(p2.x, p2.y), color); }
2366
2367 /// Draw active render-list triangles as filled polygons.
2368 void DrawSolidPolys(const RenderList &list) const {
2369 for (const auto &poly : list.polys) {
2370 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
2371 continue;
2372 }
2373 DrawFilledTriangle(poly.tlist[0], poly.tlist[1], poly.tlist[2], poly.color);
2374 }
2375 }
2376
2377 /// Draw active render-list triangles as clipped wireframes.
2378 void DrawPolys(const RenderList &list) const {
2379 for (const auto &poly : list.polys) {
2380 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
2381 continue;
2382 }
2383 DrawWireframeTriangle(poly.tlist[0], poly.tlist[1], poly.tlist[2], poly.color);
2384 }
2385 }
2386
2387 /// Draw active render-list triangles as depth-tested clipped wireframes.
2388 void DrawPolys(const RenderList &list, std::span<float> depth_buffer) const {
2389 for (const auto &poly : list.polys) {
2390 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
2391 continue;
2392 }
2393 DrawWireframeTriangle(poly.tlist[0], poly.tlist[1], poly.tlist[2], poly.color, depth_buffer);
2394 }
2395 }
2396
2397 /// Draw an object's active transformed triangles as clipped wireframes.
2398 void DrawObject(const mxObject &object) const {
2399 if ((object.state & MX_CULLED) != 0) {
2400 return;
2401 }
2402 for (const auto &poly : object.vlist) {
2403 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
2404 continue;
2405 }
2406 const auto a = static_cast<std::size_t>(poly.vert[0]);
2407 const auto b = static_cast<std::size_t>(poly.vert[1]);
2408 const auto c = static_cast<std::size_t>(poly.vert[2]);
2409 if (a >= object.trans.size() || b >= object.trans.size() || c >= object.trans.size()) {
2410 continue;
2411 }
2412 DrawWireframeTriangle(object.trans[a], object.trans[b], object.trans[c], poly.color);
2413 }
2414 }
2415
2416 /// Draw an object's active transformed triangles as depth-tested clipped wireframes.
2417 void DrawObject(const mxObject &object, std::span<float> depth_buffer) const {
2418 if ((object.state & MX_CULLED) != 0) {
2419 return;
2420 }
2421 for (const auto &poly : object.vlist) {
2422 if (poly.state == 0 || (poly.state & MX_BACKFACE) != 0) {
2423 continue;
2424 }
2425 const auto a = static_cast<std::size_t>(poly.vert[0]);
2426 const auto b = static_cast<std::size_t>(poly.vert[1]);
2427 const auto c = static_cast<std::size_t>(poly.vert[2]);
2428 if (a >= object.trans.size() || b >= object.trans.size() || c >= object.trans.size()) {
2429 continue;
2430 }
2431 DrawWireframeTriangle(object.trans[a], object.trans[b], object.trans[c], poly.color, depth_buffer);
2432 }
2433 }
2434
2435 /// End rendering and release the current plotting callbacks.
2436 void End() {
2437 plot_pixel = nullptr;
2438 plot_span = nullptr;
2439 }
2440
2441 private:
2442 std::function<void(int, int, MXCOLOR)> plot_pixel;
2443 std::function<void(int, int, int, MXCOLOR)> plot_span;
2444 };
2445
2446} // namespace mxvk
Camera and projection data for the simple software 3D pipeline.
Definition mxvk_math.h:1690
float viewport_width
Viewport width in pixels.
Definition mxvk_math.h:1747
int state
Camera state flags.
Definition mxvk_math.h:1693
float aspect_ratio
Viewport aspect ratio.
Definition mxvk_math.h:1759
float far_clip_z
Far clipping plane Z.
Definition mxvk_math.h:1726
int attr
Camera attributes.
Definition mxvk_math.h:1696
vec4D target
Camera look-at target.
Definition mxvk_math.h:1714
float view_dist
Distance from camera to view plane.
Definition mxvk_math.h:1717
float near_clip_z
Near clipping plane Z.
Definition mxvk_math.h:1723
void PerspectiveToScreen(RenderList &list) const
Convert a render list from perspective coordinates to screen coordinates.
Plane3D lt_clip_plane
Left clipping plane.
Definition mxvk_math.h:1732
void PerspectiveToScreen(mxObject &object) const
Convert an object's transformed vertices from perspective coordinates to screen coordinates.
vec4D dir
Euler camera direction in degrees.
Definition mxvk_math.h:1702
vec4D pos
Camera position.
Definition mxvk_math.h:1699
float viewport_height
Viewport height in pixels.
Definition mxvk_math.h:1750
float viewplane_width
View-plane width.
Definition mxvk_math.h:1744
void InitalizeForEuler()
Initialize camera fields for the Euler example path.
void CameraToPerspective(mxObject &object) const
Project an object's transformed vertices from camera space to perspective space.
float viewport_center_y
Viewport center Y coordinate.
Definition mxvk_math.h:1756
Mat4D mcam
World-to-camera transform matrix.
Definition mxvk_math.h:1762
Plane3D bt_clip_plane
Bottom clipping plane.
Definition mxvk_math.h:1738
float viewplane_height
View-plane height.
Definition mxvk_math.h:1741
Mat4D mscr
Screen transform matrix placeholder.
Definition mxvk_math.h:1768
void BuildUVN(int)
Build the world-to-camera matrix from the camera position and look-at target.
void WorldToCamera(RenderList &list) const
Transform a render list from world space to camera space.
vec4D u
UVN camera U basis vector.
Definition mxvk_math.h:1705
void BuildEuler(int)
Build the world-to-camera matrix from Euler direction angles.
Definition mxvk_math.h:1807
vec4D v
UVN camera V basis vector.
Definition mxvk_math.h:1708
void Init(int camera_attr, const vec4D &camera_pos, const vec4D &camera_dir, const vec4D *camera_target, float near_z, float far_z, float fov_degrees, float width, float height)
Initialize camera projection, viewport, position, and direction parameters.
Plane3D tp_clip_plane
Top clipping plane.
Definition mxvk_math.h:1735
Plane3D rt_clip_plane
Right clipping plane.
Definition mxvk_math.h:1729
Mat4D mper
Perspective transform matrix placeholder.
Definition mxvk_math.h:1765
Camera()
Construct a camera with identity matrices.
float fov
Vertical field of view in degrees.
Definition mxvk_math.h:1720
void WorldToCamera(mxObject &object) const
Transform an object's transformed vertices from world space to camera space.
float viewport_center_x
Viewport center X coordinate.
Definition mxvk_math.h:1753
void CameraToPerspective(RenderList &list) const
Project a render list from camera space to perspective space.
vec4D n
UVN camera N basis vector.
Definition mxvk_math.h:1711
Two-element column-vector storage used by 2x2 linear solves.
Definition mxvk_math.h:493
constexpr Mat1D()=default
Construct a zero-initialized 2-element vector.
float mat[2]
Matrix/vector elements.
Definition mxvk_math.h:496
void Set(float m0, float m1)
Set both elements.
constexpr Mat1D(float m0, float m1)
Construct from explicit elements.
Three-element column-vector storage used by 3x3 linear solves.
Definition mxvk_math.h:512
float mat[3]
Matrix/vector elements.
Definition mxvk_math.h:515
constexpr Mat1x3D(float m0, float m1, float m2)
Construct from explicit elements.
constexpr Mat1x3D()=default
Construct a zero-initialized 3-element vector.
constexpr Mat1x4D(float m0, float m1, float m2, float m3)
Construct from explicit elements.
float mat[4]
Matrix/vector elements.
Definition mxvk_math.h:528
constexpr Mat1x4D()=default
Construct a zero-initialized 4-element vector.
Two-by-two matrix with arithmetic, determinant, inverse, and solve helpers.
Definition mxvk_math.h:545
Mat2D()=default
Construct a zero-initialized matrix.
Mat2D operator*(const Mat2D &m) const
Multiply two 2x2 matrices.
bool Inverse(Mat2D &out) const
Compute the inverse matrix.
float Determinate() const
Compute the matrix determinant.
Mat2D operator-(const Mat2D &m) const
Subtract two matrices component-wise.
static bool Solve2x2(const Mat2D &a, Mat1D &out, const Mat1D &b)
Solve a 2x2 linear system.
void LoadIdentity()
Set this matrix to the identity matrix.
Mat2D(float m00, float m01, float m10, float m11)
Construct from explicit row-major elements.
Mat2D operator+(const Mat2D &m) const
Add two matrices component-wise.
float mat[2][2]
Matrix elements indexed as row, column.
Definition mxvk_math.h:548
void Set(float m00, float m01, float m10, float m11)
Set all matrix elements in row-major order.
Definition mxvk_math.h:557
Three-by-three matrix with multiplication, vector transform, inverse, and solve helpers.
Definition mxvk_math.h:613
float Determinate() const
Compute the matrix determinant.
vec3D MulVec(const vec3D &in) const
Transform a 3D vector by this matrix.
Mat3D operator*(const Mat3D &m) const
Multiply two 3x3 matrices.
Mat3D()=default
Construct a zero-initialized matrix.
static bool Solve3x3(const Mat3D &a, Mat1x3D &out, const Mat1x3D &b)
Solve a 3x3 linear system.
Mat3D(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22)
Construct from explicit row-major elements.
float mat[3][3]
Matrix elements indexed as row, column.
Definition mxvk_math.h:616
void Set(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22)
Set all matrix elements in row-major order.
Definition mxvk_math.h:625
void LoadIdentity()
Set this matrix to the identity matrix.
void MulVec(const vec3D &in, vec3D &out) const
Transform a 3D vector and write the result to out.
bool Inverse(Mat3D &out) const
Compute the inverse matrix.
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
bool Inverse(Mat4D &out) const
Compute the inverse matrix.
void BuildXYZ(float theta_x, float theta_y, float theta_z)
Build an XYZ Euler rotation matrix from angles in degrees.
Mat4D()=default
Construct a zero-initialized matrix.
Mat4D & operator*=(const Mat4D &m)
Multiply this matrix by another matrix in place.
void MulVec(const vec3D &in, vec3D &out) const
Transform a 3D point and write the result to out.
vec4D MulVec(const vec4D &in) const
Transform a homogeneous 4D vector by this matrix.
vec3D MulVec(const vec3D &in) const
Transform a 3D point by this matrix using W = 1.
void MulVec(std::span< const vec4D > input, std::span< vec4D > output) const
Transform a batch of homogeneous 4D vectors.
void Set(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33)
Set all matrix elements in row-major order.
Definition mxvk_math.h:718
void MulVec(const vec4D &in, vec4D &out) const
Transform a homogeneous 4D vector and write the result to out.
void LoadIdentity()
Set this matrix to the identity matrix.
Mat4D operator+(const Mat4D &m) const
Add two matrices component-wise.
Mat4D operator*(const Mat4D &m) const
Multiply two 4x4 matrices.
Mat4D(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33)
Construct from explicit row-major elements.
float mat[4][3]
Matrix elements indexed as row, column.
Definition mxvk_math.h:541
Clipped software rasterization pipeline for lines and filled triangles.
Definition mxvk_math.h:2104
void End()
End rendering and release the current plotting callbacks.
void DrawFilledTriangle(const vec2D &p0, const vec2D &p1, const vec2D &p2, MXCOLOR color) const
Draw a clipped filled triangle from 2D screen-space vertices.
int min_clip_x
Minimum clip X coordinate.
Definition mxvk_math.h:2131
void DrawFilledTriangle(const vec4D &p0, const vec4D &p1, const vec4D &p2, MXCOLOR color) const
Draw a clipped filled triangle from homogeneous screen-space vertices.
int min_clip_y
Minimum clip Y coordinate.
Definition mxvk_math.h:2134
void DrawSolidPolys(const RenderList &list) const
Draw active render-list triangles as filled polygons.
void Begin(VK_Sprite &sprite, int width, int height, int pixel_size=1)
Begin rendering to a VK_Sprite with optional square pixel size.
void DrawObject(const mxObject &object) const
Draw an object's active transformed triangles as clipped wireframes.
void DrawClipedLine(int x0, int y0, int x1, int y1, MXCOLOR color) const
Draw a clipped line. Kept with the original misspelled name for compatibility.
void DrawPolys(const RenderList &list) const
Draw active render-list triangles as clipped wireframes.
int clip_min_x
Active minimum clip X coordinate.
Definition mxvk_math.h:2137
int clip_max_x
Active maximum clip X coordinate.
Definition mxvk_math.h:2140
void DrawClippedLine(int x0, int y0, int x1, int y1, MXCOLOR color) const
Draw a clipped line.
int max_clip_y
Maximum clip Y coordinate.
Definition mxvk_math.h:2128
void DrawObject(const mxObject &object, std::span< float > depth_buffer) const
Draw an object's active transformed triangles as depth-tested clipped wireframes.
void DrawPolys(const RenderList &list, std::span< float > depth_buffer) const
Draw active render-list triangles as depth-tested clipped wireframes.
void DrawWireframeTriangle(const vec4D &first, const vec4D &second, const vec4D &third, MXCOLOR color, std::span< float > depth_buffer) const
Draw a clipped wireframe triangle with perspective-correct depth testing.
void DrawDepthTestedLine(const vec4D &first, const vec4D &second, MXCOLOR color, std::span< float > depth_buffer) const
Draw a clipped screen-space line with perspective-correct depth testing.
bool ClipLine(int &x0, int &y0, int &x1, int &y1) const
Clip a line segment to the active clip rectangle.
LINE_CODE
Cohen-Sutherland region codes for line clipping.
Definition mxvk_math.h:2107
@ CODE_E
East/right code.
Definition mxvk_math.h:2118
@ CODE_C
Center/inside code.
Definition mxvk_math.h:2109
@ CODE_S
South/bottom code.
Definition mxvk_math.h:2115
@ CODE_W
West/left code.
Definition mxvk_math.h:2121
@ CODE_N
North/top code.
Definition mxvk_math.h:2112
void Begin(int width, int height, std::function< void(int, int, MXCOLOR)> plotter)
Begin rendering with a custom pixel plotter.
void Begin(SDL_Renderer *renderer, int width, int height)
Begin rendering to an SDL renderer.
int clip_min_y
Active minimum clip Y coordinate.
Definition mxvk_math.h:2143
int ComputeCode(int x, int y) const
Compute the Cohen-Sutherland region code for a point.
int max_clip_x
Maximum clip X coordinate.
Definition mxvk_math.h:2125
void DrawWireframeTriangle(const vec4D &first, const vec4D &second, const vec4D &third, MXCOLOR color) const
Draw a clipped wireframe triangle without depth testing.
int clip_max_y
Active maximum clip Y coordinate.
Definition mxvk_math.h:2146
Quaternion used for 3D rotations.
Definition mxvk_math.h:1048
void Inverse()
Invert this quaternion in place.
float w
Scalar component.
Definition mxvk_math.h:1060
void InverseNormal()
Invert a unit quaternion by conjugating it.
constexpr QuatType()
Construct the identity quaternion.
float Norm2() const
Compute the squared norm.
constexpr QuatType operator*(const QuatType &q) const
Multiply two quaternions.
float z
Z component of the vector part.
Definition mxvk_math.h:1057
constexpr QuatType operator+(const QuatType &q) const
Add two quaternions component-wise.
constexpr QuatType operator-(const QuatType &q) const
Subtract two quaternions component-wise.
float Norm() const
Compute the norm.
void Scale(float f)
Scale all quaternion components in place.
void vec4DthetaQuat(float theta_degrees, const vec4D &axis)
Build a quaternion from a 4D axis vector and angle in degrees.
void vec3DthetaQuat(float theta_degrees, const vec3D &axis)
Build a quaternion from an axis and angle in degrees.
float x
X component of the vector part.
Definition mxvk_math.h:1051
void EulerZYX(float theta_x, float theta_y, float theta_z)
Build a quaternion from Euler angles in ZYX order, with angles in degrees.
void Normalize()
Normalize this quaternion in place, or reset it to identity if it is too small.
void Conj()
Conjugate this quaternion in place.
constexpr QuatType(float x_value, float y_value, float z_value, float w_value)
Construct from explicit quaternion components.
QuatType TripleProduct(const QuatType &p1, const QuatType &p2) const
Return the quaternion product (*this * p1) * p2.
void QuatToVec3D(float *theta_degrees, vec3D &axis) const
Convert this quaternion to axis-angle form.
float y
Y component of the vector part.
Definition mxvk_math.h:1054
QuatType & operator*=(const QuatType &q)
Multiply this quaternion by another quaternion in place.
Flat list of triangles prepared for transformation and rasterization.
Definition mxvk_math.h:1232
void RemoveFaces(const vec4D &pos)
Mark back-facing triangles relative to a view position.
int num_polys
Cached polygon count matching polys.size().
Definition mxvk_math.h:1238
void ModelToWorld(const vec4D &pos, int type)
Translate model-space vertices into world-space transformed vertices.
std::vector< Triangle > polys
Triangle storage.
Definition mxvk_math.h:1235
void TransformRenderList(const Mat4D &mrot, int type)
Transform active non-backface triangles by a matrix.
void Reset()
Clear all triangles from the render list.
void BuildRenderList(const Triangle &triangle)
Append one triangle to the render list.
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.
Simple mesh object loaded from PLG-style indexed triangle data.
Definition mxvk_math.h:1300
void SetState(int new_state)
Replace the object state flags.
bool LoadMX(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation)
Load an MX mesh file using the PLG loader compatibility path.
bool LoadMTL(const std::string &path)
Replace this object's material library with an MTL file.
float ComputeRad()
Compute and cache average and maximum local-space radii.
Definition mxvk_math.h:1664
float max_rad
Maximum radius from the local origin.
Definition mxvk_math.h:1312
vec4D world_pos
Object world position.
Definition mxvk_math.h:1315
std::vector< vec2D > texcoords
Optional texture coordinates corresponding to local-space vertices.
Definition mxvk_math.h:1342
float avg_rad
Average radius from the local origin.
Definition mxvk_math.h:1309
std::string material_library_path
Resolved path of the OBJ material library.
Definition mxvk_math.h:1354
vec4D ux
Local X basis vector.
Definition mxvk_math.h:1321
bool LoadOBJ(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation)
Load a Wavefront OBJ mesh and its referenced MTL material library.
int num_polys
Number of loaded polygons.
Definition mxvk_math.h:1333
std::vector< OBJMaterial > materials
Materials loaded from the OBJ material library.
Definition mxvk_math.h:1351
int attr
Object attributes.
Definition mxvk_math.h:1306
void BuildRenderList(RenderList &list) const
Append this object's triangles to a render list.
std::string object_name
Object name loaded from the model file.
Definition mxvk_math.h:1348
vec4D dir
Object direction.
Definition mxvk_math.h:1318
void Reset()
Reset object and polygon state to active.
int num_vertices
Number of loaded vertices.
Definition mxvk_math.h:1330
int state
Object state flags.
Definition mxvk_math.h:1303
std::vector< vec4D > local
Local-space vertices.
Definition mxvk_math.h:1336
std::vector< Triangle > vlist
Indexed triangle list.
Definition mxvk_math.h:1345
vec4D uy
Local Y basis vector.
Definition mxvk_math.h:1324
void RemoveFaces(const vec4D &pos)
Mark object polygons that face away from a view position.
std::vector< vec4D > trans
Transformed vertices.
Definition mxvk_math.h:1339
void ModelToWorld(int type=0)
Convert local or transformed vertices to world space.
void TransformObject(const Mat4D &mrot, int type=0)
Apply a transform to local vertices, transformed vertices, or local-to-transformed output.
vec4D uz
Local Z basis vector.
Definition mxvk_math.h:1327
bool LoadPLG(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation)
Load a PLG mesh file.
Two-dimensional float vector with common arithmetic helpers.
Definition mxvk_math.h:158
float Length() const
Compute the Euclidean length of this vector.
constexpr vec2D()
Construct the zero vector.
float DotProduct(const vec2D &v) const
Compute the dot product with another vector.
float x
X coordinate.
Definition mxvk_math.h:161
constexpr float DotProduct(const vec2D &v) const
Compute the dot product with another vector.
Definition mxvk_math.h:213
void Normalize(vec2D &v) const
Write a normalized copy of this vector to v.
void Set(float x_value, float y_value)
Set both vector coordinates.
void Normalize()
Normalize this vector in place, or reset it to zero if it is too short.
vec2D & operator+=(const vec2D &v)
Add another vector to this vector.
float y
Y coordinate.
Definition mxvk_math.h:164
vec2D & operator-=(const vec2D &v)
Subtract another vector from this vector.
vec2D operator+(const vec2D &v) const
Add two vectors component-wise.
float Cos(const vec2D &v) const
Compute the cosine of the angle between this vector and another vector.
vec2D operator*(float k) const
Scale this vector by a scalar.
vec2D Scale(float k) const
Return a scaled copy of this vector.
std::string Print(const std::string &name="v") const
Format this vector as a named angle-bracket tuple.
constexpr vec2D(float x_value, float y_value)
Construct a vector from explicit coordinates.
vec2D operator-(const vec2D &v) const
Subtract two vectors component-wise.
vec2D & operator=(const vec2D &)=default
void ScaleThis(float k)
Scale this vector in place.
Three-dimensional float vector with arithmetic, dot, and cross-product helpers.
Definition mxvk_math.h:256
vec3D operator-(const vec3D &v) const
Subtract two vectors component-wise.
vec3D operator+(const vec3D &v) const
Add two vectors component-wise.
constexpr vec3D(float x_value, float y_value, float z_value)
Construct a vector from explicit coordinates.
float DotProduct(const vec3D &v) const
Compute the dot product with another vector.
vec3D Scale(float k) const
Return a scaled copy of this vector.
constexpr float DotProduct(const vec3D &v) const
Compute the dot product with another vector.
Definition mxvk_math.h:318
float z
Z coordinate.
Definition mxvk_math.h:265
vec3D & operator=(const vec3D &)=default
float x
X coordinate.
Definition mxvk_math.h:259
vec3D & operator-=(const vec3D &v)
Subtract another vector from this vector.
constexpr vec3D()
Construct the zero vector.
vec3D CrossProduct(const vec3D &v) const
Compute the right-handed cross product with another vector.
float Cos(const vec3D &v) const
Compute the cosine of the angle between this vector and another vector.
void Set(float x_value, float y_value, float z_value)
Set all vector coordinates.
vec3D & operator+=(const vec3D &v)
Add another vector to this vector.
vec3D operator*(float k) const
Scale this vector by a scalar.
float Length() const
Compute the Euclidean length of this vector.
void Normalize(vec3D &v) const
Write a normalized copy of this vector to v.
void Normalize()
Normalize this vector in place, or reset it to zero if it is too short.
void ScaleThis(float k)
Scale this vector in place.
std::string Print(const std::string &name="v") const
Format this vector as a named angle-bracket tuple.
float y
Y coordinate.
Definition mxvk_math.h:262
Four-dimensional float vector used for homogeneous 3D coordinates.
Definition mxvk_math.h:363
constexpr vec4D()
Construct the homogeneous origin.
float y
Y coordinate.
Definition mxvk_math.h:369
float x
X coordinate.
Definition mxvk_math.h:366
float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
float Length() const
Compute the 3D Euclidean length, ignoring the W component.
float Cos(const vec4D &v) const
Compute the cosine of the angle between the 3D components of two vectors.
float w
Homogeneous W coordinate.
Definition mxvk_math.h:375
vec4D operator*(float k) const
Scale this vector by a scalar.
void Normalize()
Normalize the 3D components in place while preserving W.
vec4D CrossProduct(const vec4D &v) const
Compute the 3D cross product and return it as a direction with W set to 0.
constexpr vec4D(float x_value, float y_value, float z_value, float w_value=1.0f)
Construct a homogeneous vector from explicit coordinates.
vec4D operator-(const vec4D &v) const
Subtract two vectors component-wise.
vec4D & operator-=(const vec4D &v)
Subtract another vector from this vector.
constexpr float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
Definition mxvk_math.h:438
vec4D operator*(const vec4D &v) const
Multiply two vectors component-wise.
void Normalize(vec4D &v) const
Write a normalized copy of this vector to v.
float z
Z coordinate.
Definition mxvk_math.h:372
vec4D Build(const vec4D &from, const vec4D &to) const
Build a direction vector from from to to with W set to 0.
void Build(const vec4D &to)
Replace this vector with the direction from this point to to.
vec4D & operator+=(const vec4D &v)
Add another vector to this vector.
vec4D operator+(const vec4D &v) const
Add two vectors component-wise.
std::string Print(const std::string &name="v") const
Format this vector as a named angle-bracket tuple.
void Set(const vec4D &v)
Copy coordinates from another vector.
vec4D Scale(float k) const
Return a scaled copy of this vector.
void Set(float x_value, float y_value, float z_value, float w_value=1.0f)
Set all vector coordinates.
vec4D & operator=(const vec4D &)=default
void ScaleThis(float k)
Scale this vector in place.
Vulkan 2-D sprite renderer with optional custom shaders and instancing.
bool load_mtl_file(const std::string &path, std::vector< OBJMaterial > &materials, std::string &error)
bool load_obj_file(const std::string &path, OBJLoadResult &result, std::string &error)
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
int rrand(int x, int y)
Return a pseudo-random integer in the inclusive range between two bounds.
Definition mxvk_math.h:150
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
std::array< float, 361 > cos_look
Cosine lookup table with one entry per degree from 0 through 360.
Definition mxvk_math.h:95
void BuildTables()
Rebuild the sine and cosine lookup tables.
Definition mxvk_math.h:98
MXCOLOR shade_color(MXCOLOR color, float intensity)
Scale the RGB channels of a color while preserving alpha.
Definition mxvk_math.h:69
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
Definition mxvk_math.h:55
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
Definition mxvk_math.h:49
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
Definition mxvk_math.h:61
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.
Definition mxvk_math.h:1972
std::array< float, 361 > build_cos_table()
Definition mxvk_math.h:83
void draw_filled_triangle(const vec2D &p0, const vec2D &p1, const vec2D &p2, MXCOLOR color, PlotPixel &&plot_pixel)
Rasterize a filled triangle by testing pixels against edge functions.
Definition mxvk_math.h:1983
std::ostream & operator<<(std::ostream &out, const vec2D &v)
Write a 2D vector to a stream using vec2D::Print().
Definition mxvk_math.h:250
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
Definition mxvk_math.h:37
float deg2rad(float ang)
Convert degrees to radians.
Definition mxvk_math.h:109
float fast_cosf(float theta_degrees)
Approximate cosine using the degree lookup table with linear interpolation.
Definition mxvk_math.h:119
void draw_filled_triangle_spans(vec2D p0, vec2D p1, vec2D p2, MXCOLOR color, DrawSpan &&draw_span)
Definition mxvk_math.h:2101
@ MX_ACTIVE
Active object or polygon.
Definition mxvk_math.h:1219
@ MX_CULLED
Object or polygon is culled.
Definition mxvk_math.h:1228
@ MX_BACKFACE
Polygon is marked as a backface.
Definition mxvk_math.h:1225
@ MX_VISIBLE
Visible object or polygon.
Definition mxvk_math.h:1222
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
Definition mxvk_math.h:58
constexpr float PI
Mathematical constant pi as a single-precision value.
Definition mxvk_math.h:34
std::array< float, 361 > sin_look
Sine lookup table with one entry per degree from 0 through 360.
Definition mxvk_math.h:92
float fast_sinf(float theta_degrees)
Approximate sine using the degree lookup table with linear interpolation.
Definition mxvk_math.h:134
void draw_filled_triangle_spans_clipped(vec2D p0, vec2D p1, vec2D p2, int clip_min_y, int clip_max_y, MXCOLOR color, DrawSpan &&draw_span)
Rasterize a filled triangle as horizontal spans.
Definition mxvk_math.h:2024
std::istream & operator>>(std::istream &in, vec2D &v)
Read a 2D vector from a stream as two scalar coordinates.
Definition mxvk_math.h:253
void draw_line(int x0, int y0, int x1, int y1, MXCOLOR color, PlotPixel &&plot_pixel)
Draw a line with Bresenham-style integer stepping.
Definition mxvk_math.h:1941
float rad2deg(float rad)
Convert radians to degrees.
Definition mxvk_math.h:112
std::array< float, 361 > build_sin_table()
Definition mxvk_math.h:75
Cylindrical coordinate.
Definition mxvk_math.h:1024
float z
Height coordinate.
Definition mxvk_math.h:1032
float r
Radial distance.
Definition mxvk_math.h:1026
float theta
Azimuth angle in degrees for this framework's math helpers.
Definition mxvk_math.h:1029
Wavefront material data used by the software rasterizer.
std::array< float, 3 > diffuse
void Set(const vec3D &point, const vec3D &normal, bool normalize)
Set the plane point and normal, optionally normalizing the normal.
Plane3D()=default
Construct an uninitialized plane.
vec3D p0
Point on the plane.
Definition mxvk_math.h:993
Plane3D(const vec3D &point, const vec3D &normal)
Construct from a point and normal vector.
vec3D v
Plane normal.
Definition mxvk_math.h:996
float r
Radial distance.
Definition mxvk_math.h:1017
float theta
Angle in degrees for this framework's math helpers.
Definition mxvk_math.h:1020
Pixel plotter adapter that writes packed MXVK colors to an SDL renderer.
Definition mxvk_math.h:1901
void operator()(int x, int y, MXCOLOR color) const
Plot one pixel if the renderer is valid.
SDL_Renderer * renderer
SDL renderer receiving plotted pixels.
Definition mxvk_math.h:1903
Spherical coordinate.
Definition mxvk_math.h:1036
float p
Radial distance.
Definition mxvk_math.h:1038
float theta
Azimuth angle in degrees for this framework's math helpers.
Definition mxvk_math.h:1041
float phi
Inclination angle in degrees for this framework's math helpers.
Definition mxvk_math.h:1044
Triangle primitive used by the simple software rendering pipeline.
Definition mxvk_math.h:1187
std::string material_name
Wavefront material name retained for later MTL replacement.
Definition mxvk_math.h:1210
int attr
Application-defined polygon attributes.
Definition mxvk_math.h:1198
std::string source_object_name
Wavefront object name from the o section containing this triangle.
Definition mxvk_math.h:1213
vec4D tlist[3]
Transformed vertex positions.
Definition mxvk_math.h:1192
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
int state
Polygon state flags.
Definition mxvk_math.h:1201
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
Pixel plotter adapter that draws square pixels into a VK_Sprite.
Definition mxvk_math.h:1916
void operator()(int x, int y, MXCOLOR) const
Plot one square pixel if the sprite is valid.
int size
Square pixel size in sprite coordinates.
Definition mxvk_math.h:1921
VK_Sprite * sprite
Sprite receiving plotted pixels.
Definition mxvk_math.h:1918
std::vector< OBJMaterial > materials
std::vector< OBJTriangle > triangles
std::array< OBJVertex, 3 > vertices
std::array< float, 3 > position
std::array< float, 2 > texcoord
Parametric 2D line segment represented by a start point, end point, and direction.
Definition mxvk_math.h:875
paramLine2D(const vec2D &start, const vec2D &end, const vec2D &dir)
Construct from explicit endpoints and direction.
paramLine2D()=default
Construct an uninitialized line segment.
vec2D ComputePoint(float t, vec2D &out) const
Compute the point p0 + v * t and write it to out.
void Set(const vec2D &start, const vec2D &end, const vec2D &dir)
Set explicit endpoints and direction.
Definition mxvk_math.h:892
int Intersect(const paramLine2D &line, vec2D &out) const
Intersect this segment with another segment and compute the point.
vec2D p0
Segment start point.
Definition mxvk_math.h:877
int Intersect(const paramLine2D &line, float &t_this, float &t_other) const
Intersect this segment with another parametric segment.
void Init(const vec2D &start, const vec2D &end)
Initialize from endpoints and derive the direction vector.
vec2D p1
Segment end point.
Definition mxvk_math.h:880
vec2D ComputePoint(float t) const
Compute the point p0 + v * t.
vec2D v
Direction vector, commonly p1 - p0.
Definition mxvk_math.h:883
vec3D ComputePoint(float t) const
Compute the point p0 + v * t.
vec3D ComputePoint(float t, vec3D &out) const
Compute the point p0 + v * t and write it to out.
paramLine3D()=default
Construct an uninitialized line segment.
vec3D p1
Segment end point.
Definition mxvk_math.h:955
paramLine3D(const vec3D &start, const vec3D &end, const vec3D &dir)
Construct from explicit endpoints and direction.
vec3D p0
Segment start point.
Definition mxvk_math.h:952
void Set(const vec3D &start, const vec3D &end, const vec3D &dir)
Set explicit endpoints and direction.
Definition mxvk_math.h:967
vec3D v
Direction vector, commonly p1 - p0.
Definition mxvk_math.h:958
void Init(const vec3D &start, const vec3D &end)
Initialize from endpoints and derive the direction vector.