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