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