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