24#include <unordered_map>
36 inline constexpr float PI = 3.14159265358979323846f;
39 inline constexpr float EPSILON = 1.0e-5f;
52 return 0xFF000000u | ((
static_cast<MXCOLOR>(r) & 0xFFu) << 16u) | ((
static_cast<MXCOLOR>(g) & 0xFFu) << 8u) | (
static_cast<MXCOLOR>(b) & 0xFFu);
56 [[nodiscard]]
inline constexpr std::uint8_t
color_r(
MXCOLOR color) {
57 return static_cast<std::uint8_t
>((color >> 16u) & 0xFFu);
61 [[nodiscard]]
inline constexpr std::uint8_t
color_g(
MXCOLOR color) {
62 return static_cast<std::uint8_t
>((color >> 8u) & 0xFFu);
66 [[nodiscard]]
inline constexpr std::uint8_t
color_b(
MXCOLOR color) {
67 return static_cast<std::uint8_t
>(color & 0xFFu);
71 [[nodiscard]]
inline constexpr std::uint8_t
color_a(
MXCOLOR color) {
72 return static_cast<std::uint8_t
>((color >> 24u) & 0xFFu);
82 intensity = std::clamp(intensity, 0.0f, 1.0f);
83 const auto scale = [intensity](std::uint8_t component) {
84 return static_cast<int>(std::clamp(
static_cast<float>(component) * intensity, 0.0f, 255.0f));
93 std::array<float, 361> values{};
94 for (
int ang = 0; ang <= 360; ++ang) {
95 values[
static_cast<std::size_t
>(ang)] = std::sin(
static_cast<float>(ang) *
PI / 180.0f);
101 std::array<float, 361> values{};
102 for (
int ang = 0; ang <= 360; ++ang) {
103 values[
static_cast<std::size_t
>(ang)] = std::cos(
static_cast<float>(ang) *
PI / 180.0f);
116 std::cout <<
"mxvk_math_eigen: building trigonometric lookup tables\n";
117 for (
int ang = 0; ang <= 360; ++ang) {
118 const float theta =
static_cast<float>(ang) *
PI / 180.0f;
119 cos_look[
static_cast<std::size_t
>(ang)] = std::cos(theta);
120 sin_look[
static_cast<std::size_t
>(ang)] = std::sin(theta);
122 std::cout <<
"mxvk_math_eigen: trigonometric lookup tables ready (361 entries)\n";
126 [[nodiscard]]
inline float deg2rad(
float ang) {
127 return ang *
PI / 180.0f;
131 [[nodiscard]]
inline float rad2deg(
float rad) {
132 return rad * 180.0f /
PI;
140 [[nodiscard]]
inline float fast_cosf(
float theta_degrees) {
141 if (!std::isfinite(theta_degrees)) {
142 return std::numeric_limits<float>::quiet_NaN();
144 theta_degrees = std::fmod(theta_degrees, 360.0f);
145 if (theta_degrees < 0.0f) {
146 theta_degrees += 360.0f;
148 if (theta_degrees >= 360.0f) {
149 theta_degrees = 0.0f;
151 const int theta_int =
static_cast<int>(theta_degrees);
152 const int next_index = (theta_int + 1) % 360;
153 const float theta_frac = theta_degrees -
static_cast<float>(theta_int);
154 return cos_look[
static_cast<std::size_t
>(theta_int)] + theta_frac * (
cos_look[
static_cast<std::size_t
>(next_index)] -
cos_look[
static_cast<std::size_t
>(theta_int)]);
162 [[nodiscard]]
inline float fast_sinf(
float theta_degrees) {
163 if (!std::isfinite(theta_degrees)) {
164 return std::numeric_limits<float>::quiet_NaN();
166 theta_degrees = std::fmod(theta_degrees, 360.0f);
167 if (theta_degrees < 0.0f) {
168 theta_degrees += 360.0f;
170 if (theta_degrees >= 360.0f) {
171 theta_degrees = 0.0f;
173 const int theta_int =
static_cast<int>(theta_degrees);
174 const int next_index = (theta_int + 1) % 360;
175 const float theta_frac = theta_degrees -
static_cast<float>(theta_int);
176 return sin_look[
static_cast<std::size_t
>(theta_int)] + theta_frac * (
sin_look[
static_cast<std::size_t
>(next_index)] -
sin_look[
static_cast<std::size_t
>(theta_int)]);
185 [[nodiscard]]
inline int rrand(
int x,
int y) {
189 return x + (std::rand() % (y - x + 1));
205 constexpr vec2D(
float x_value,
float y_value) :
x(x_value),
y(y_value) {}
208 void Set(
float x_value,
float y_value) {
217 return FromEigen(ToEigen() + v.ToEigen());
222 FromEigen(ToEigen() + v.ToEigen(), *
this);
228 return FromEigen(ToEigen() - v.ToEigen());
233 FromEigen(ToEigen() - v.ToEigen(), *
this);
239 return FromEigen(ToEigen() * k);
249 FromEigen(ToEigen() * k, *
this);
254 return ToEigen().dot(v.ToEigen());
259 return ToEigen().norm();
264 const float length =
Length();
266 FromEigen(Eigen::Vector2f::Zero(), *
this);
269 FromEigen(ToEigen().normalized(), *
this);
280 const float denom =
Length() * v.Length();
285 [[nodiscard]] std::string
Print(
const std::string &name =
"v")
const {
286 std::ostringstream out;
287 out << name <<
'<' <<
x <<
',' <<
y <<
'>';
292 [[nodiscard]] Eigen::Vector2f ToEigen()
const {
296 [[nodiscard]]
static vec2D FromEigen(
const Eigen::Vector2f &v) {
297 return {v.x(), v.y()};
300 static void FromEigen(
const Eigen::Vector2f &v,
vec2D &out) {
301 out.Set(v.x(), v.y());
307 return out << v.Print();
312 return in >> v.x >> v.y;
331 constexpr vec3D(
float x_value,
float y_value,
float z_value) :
x(x_value),
y(y_value),
z(z_value) {}
334 void Set(
float x_value,
float y_value,
float z_value) {
344 return FromEigen(ToEigen() + v.ToEigen());
349 FromEigen(ToEigen() + v.ToEigen(), *
this);
355 return FromEigen(ToEigen() - v.ToEigen());
360 FromEigen(ToEigen() - v.ToEigen(), *
this);
366 return FromEigen(ToEigen() * k);
376 FromEigen(ToEigen() * k, *
this);
381 return ToEigen().dot(v.ToEigen());
386 return FromEigen(ToEigen().cross(v.ToEigen()));
391 return ToEigen().norm();
396 const float len =
Length();
398 FromEigen(Eigen::Vector3f::Zero(), *
this);
401 FromEigen(ToEigen().normalized(), *
this);
412 const float denom =
Length() * v.Length();
417 [[nodiscard]] std::string
Print(
const std::string &name =
"v")
const {
418 std::ostringstream out;
419 out << name <<
'<' <<
x <<
',' <<
y <<
',' <<
z <<
'>';
424 [[nodiscard]] Eigen::Vector3f ToEigen()
const {
428 [[nodiscard]]
static vec3D FromEigen(
const Eigen::Vector3f &v) {
429 return {v.x(), v.y(), v.z()};
432 static void FromEigen(
const Eigen::Vector3f &v,
vec3D &out) {
433 out.Set(v.x(), v.y(), v.z());
439 return out << v.Print();
444 return in >> v.x >> v.y >> v.z;
463 constexpr vec4D() :
x(0.0f),
y(0.0f),
z(0.0f),
w(1.0f) {}
466 constexpr vec4D(
float x_value,
float y_value,
float z_value,
float w_value = 1.0f) :
x(x_value),
y(y_value),
z(z_value),
w(w_value) {}
469 void Set(
float x_value,
float y_value,
float z_value,
float w_value = 1.0f) {
485 return {
x + v.x,
y + v.y,
z + v.z,
w + v.w};
499 return {
x - v.x,
y - v.y,
z - v.z,
w - v.w};
513 return {
x * k,
y * k,
z * k,
w * k};
518 return {
x * v.x,
y * v.y,
z * v.z,
w * v.w};
536 return x * v.x +
y * v.y +
z * v.z;
556 const float len =
Length();
574 const float denom =
Length() * v.Length();
580 *
this =
Build(*
this, to);
585 return {to.x - from.x, to.y - from.y, to.z - from.z, 0.0f};
589 [[nodiscard]] std::string
Print(
const std::string &name =
"v")
const {
590 std::ostringstream out;
591 out << name <<
'<' <<
x <<
',' <<
y <<
',' <<
z <<
',' <<
w <<
'>';
597 inline std::ostream &
operator<<(std::ostream &out,
const vec4D &v) {
598 return out << v.Print();
603 return in >> v.x >> v.y >> v.z >> v.w;
616 constexpr Mat1D(
float m0,
float m1) :
mat{m0, m1} {}
619 void Set(
float m0,
float m1) {
635 constexpr Mat1x3D(
float m0,
float m1,
float m2) :
mat{m0, m1, m2} {}
648 constexpr Mat1x4D(
float m0,
float m1,
float m2,
float m3) :
mat{m0, m1, m2, m3} {}
668 Mat2D(
float m00,
float m01,
float m10,
float m11) {
669 Set(m00, m01, m10, m11);
673 void Set(
float m00,
float m01,
float m10,
float m11) {
674 ToEigen() << m00, m01, m10, m11;
679 ToEigen().setIdentity();
684 return FromEigen(ToEigen() + m.ToEigen());
689 return FromEigen(ToEigen() - m.ToEigen());
694 return FromEigen(ToEigen() * m.ToEigen());
699 return ToEigen().determinant();
712 out = FromEigen(ToEigen().inverse());
724 const float d = a.Determinate();
728 const Eigen::Vector2f rhs(b.mat[0], b.mat[1]);
729 const Eigen::Vector2f result = a.ToEigen().transpose().partialPivLu().solve(rhs);
730 out.Set(result.x(), result.y());
735 using EigenMatrix = Eigen::Matrix<float, 2, 2, Eigen::RowMajor>;
737 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() {
738 return Eigen::Map<EigenMatrix>(&
mat[0][0]);
741 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen()
const {
742 return Eigen::Map<const EigenMatrix>(&
mat[0][0]);
745 template <
typename Derived>
746 [[nodiscard]]
static Mat2D FromEigen(
const Eigen::MatrixBase<Derived> &m) {
763 Mat3D(
float m00,
float m01,
float m02,
float m10,
float m11,
float m12,
float m20,
float m21,
float m22) {
764 Set(m00, m01, m02, m10, m11, m12, m20, m21, m22);
768 void Set(
float m00,
float m01,
float m02,
float m10,
float m11,
float m12,
float m20,
float m21,
float m22) {
769 ToEigen() << m00, m01, m02, m10, m11, m12, m20, m21, m22;
774 ToEigen().setIdentity();
779 return FromEigen(ToEigen() * m.ToEigen());
784 const Eigen::Vector3f result = ToEigen().transpose() * Eigen::Vector3f(in.x, in.y, in.z);
785 return {result.x(), result.y(), result.z()};
795 return ToEigen().determinant();
808 out = FromEigen(ToEigen().inverse());
820 if (std::fabs(a.Determinate()) <=
EPSILON) {
823 const Eigen::Vector3f rhs(b.mat[0], b.mat[1], b.mat[2]);
824 const Eigen::Vector3f result = a.ToEigen().transpose().partialPivLu().solve(rhs);
825 Eigen::Map<Eigen::Vector3f>(out.mat) = result;
830 using EigenMatrix = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;
832 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() {
833 return Eigen::Map<EigenMatrix>(&
mat[0][0]);
836 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen()
const {
837 return Eigen::Map<const EigenMatrix>(&
mat[0][0]);
840 template <
typename Derived>
841 [[nodiscard]]
static Mat3D FromEigen(
const Eigen::MatrixBase<Derived> &m) {
858 Mat4D(
float m00,
float m01,
float m02,
float m03,
float m10,
float m11,
float m12,
float m13,
float m20,
float m21,
float m22,
float m23,
float m30,
float m31,
float m32,
float m33) {
859 Set(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33);
863 void Set(
float m00,
float m01,
float m02,
float m03,
float m10,
float m11,
float m12,
float m13,
float m20,
float m21,
float m22,
float m23,
float m30,
float m31,
float m32,
float m33) {
864 ToEigen() << m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33;
869 ToEigen().setIdentity();
874 return FromEigen(ToEigen() + m.ToEigen());
879 return FromEigen(ToEigen() * m.ToEigen());
890 const Eigen::Vector4f result = ToEigen().transpose() * Eigen::Vector4f(in.x, in.y, in.z, in.w);
891 return {result.x(), result.y(), result.z(), result.w()};
908 void MulVec(std::span<const vec4D> input, std::span<vec4D> output)
const {
909 if (input.size() != output.size()) {
910 throw std::invalid_argument(
"Mat4D::MulVec batch spans must have equal sizes");
916 static_assert(std::is_standard_layout_v<vec4D>);
917 static_assert(
sizeof(
vec4D) ==
sizeof(float) * 4);
918 using VertexMatrix = Eigen::Matrix<float, 4, Eigen::Dynamic, Eigen::ColMajor>;
919 const Eigen::Index vertex_count =
static_cast<Eigen::Index
>(input.size());
920 const Eigen::Map<const VertexMatrix, Eigen::Unaligned> input_matrix(&input.front().x, 4, vertex_count);
921 Eigen::Map<VertexMatrix, Eigen::Unaligned> output_matrix(&output.front().x, 4, vertex_count);
922 output_matrix.noalias() = ToEigen().transpose() * input_matrix;
928 return {r.x, r.y, r.z};
942 Eigen::FullPivLU<EigenMatrix> decomposition(ToEigen());
943 decomposition.setThreshold(
EPSILON);
944 if (!decomposition.isInvertible()) {
947 out = FromEigen(decomposition.inverse());
952 void BuildXYZ(
float theta_x,
float theta_y,
float theta_z) {
953 const float cx = std::cos(
deg2rad(theta_x));
954 const float sx = std::sin(
deg2rad(theta_x));
955 const float cy = std::cos(
deg2rad(theta_y));
956 const float sy = std::sin(
deg2rad(theta_y));
957 const float cz = std::cos(
deg2rad(theta_z));
958 const float sz = std::sin(
deg2rad(theta_z));
960 Mat4D mx(1, 0, 0, 0, 0, cx, sx, 0, 0, -sx, cx, 0, 0, 0, 0, 1);
961 Mat4D my(cy, 0, -sy, 0, 0, 1, 0, 0, sy, 0, cy, 0, 0, 0, 0, 1);
962 Mat4D mz(cz, sz, 0, 0, -sz, cz, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
963 *
this =
mx * my * mz;
967 using EigenMatrix = Eigen::Matrix<float, 4, 4, Eigen::RowMajor>;
969 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() {
970 return Eigen::Map<EigenMatrix>(&
mat[0][0]);
973 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen()
const {
974 return Eigen::Map<const EigenMatrix>(&
mat[0][0]);
977 template <
typename Derived>
978 [[nodiscard]]
static Mat4D FromEigen(
const Eigen::MatrixBase<Derived> &m) {
1001 Set(start, end, dir);
1037 Eigen::Matrix2f directions;
1038 directions <<
v.x, line.v.x,
v.y, line.v.y;
1039 const float det = directions.determinant();
1040 if (std::fabs(det) <=
EPSILON) {
1043 const Eigen::Vector2f delta(line.p0.x -
p0.x, line.p0.y -
p0.y);
1044 const Eigen::Vector2f parameters = directions.partialPivLu().solve(delta);
1045 t_this = parameters.x();
1046 t_other = -parameters.y();
1047 return (t_this >= 0.0f && t_this <= 1.0f && t_other >= 0.0f && t_other <= 1.0f) ? 1 : 2;
1057 float t_this = 0.0f;
1058 float t_other = 0.0f;
1059 const int result =
Intersect(line, t_this, t_other);
1068 struct paramLine3D {
1083 Set(start, end, dir);
1188 constexpr QuatType(
float x_value,
float y_value,
float z_value,
float w_value) :
x(x_value),
y(y_value),
z(z_value),
w(w_value) {}
1192 return {
x + q.x,
y + q.y,
z + q.z,
w + q.w};
1197 return {
x - q.x,
y - q.y,
z - q.z,
w - q.w};
1202 return {
w * q.x +
x * q.w +
y * q.z -
z * q.y,
1203 w * q.y -
x * q.z +
y * q.w +
z * q.x,
1204 w * q.z +
x * q.y -
y * q.x +
z * q.w,
1205 w * q.w -
x * q.x -
y * q.y -
z * q.z};
1231 return w *
w +
x *
x +
y *
y +
z *
z;
1236 return std::sqrt(
Norm2());
1241 const float norm =
Norm();
1252 const float n2 =
Norm2();
1271 return (*
this * p1) * p2;
1278 const float half =
deg2rad(theta_degrees) * 0.5f;
1279 const float s = std::sin(half);
1288 vec3D n(axis.x, axis.y, axis.z);
1293 void EulerZYX(
float theta_x,
float theta_y,
float theta_z) {
1294 const float hx =
deg2rad(theta_x) * 0.5f;
1295 const float hy =
deg2rad(theta_y) * 0.5f;
1296 const float hz =
deg2rad(theta_z) * 0.5f;
1297 const float cx = std::cos(hx);
1298 const float sx = std::sin(hx);
1299 const float cy = std::cos(hy);
1300 const float sy = std::sin(hy);
1301 const float cz = std::cos(hz);
1302 const float sz = std::sin(hz);
1303 w = cz * cy * cx + sz * sy * sx;
1304 x = cz * cy * sx - sz * sy * cx;
1305 y = cz * sy * cx + sz * cy * sx;
1306 z = sz * cy * cx - cz * sy * sx;
1313 const float s = std::sqrt(std::max(0.0f, 1.0f - q.w * q.w));
1315 axis.Set(1.0f, 0.0f, 0.0f);
1317 axis.Set(q.x / s, q.y / s, q.z / s);
1319 if (theta_degrees !=
nullptr) {
1320 *theta_degrees =
rad2deg(2.0f * std::acos(std::clamp(q.w, -1.0f, 1.0f)));
1374 std::vector<Triangle>
polys;
1390 for (
auto &poly :
polys) {
1391 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1394 for (
auto &vertex : poly.vlist) {
1395 vertex = mrot.MulVec(vertex);
1402 for (
auto &poly :
polys) {
1403 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1408 const vec4D n = u.CrossProduct(v);
1410 if (n.DotProduct(view) <= 0.0f) {
1421 for (
auto &poly :
polys) {
1422 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1425 for (
int i = 0; i < 3; ++i) {
1426 poly.tlist[i] = poly.vlist[i] + pos;
1433 polys.push_back(triangle);
1475 std::vector<vec4D>
local;
1478 std::vector<vec4D>
trans;
1484 std::vector<Triangle>
vlist;
1508 std::cout <<
"mxvk_math_eigen: loading PLG model: " << path <<
'\n';
1509 const auto load_failed = [&path](
const char *reason) {
1510 std::cerr <<
"mxvk_math_eigen: failed to load PLG model '" << path <<
"': " << reason <<
'\n';
1514 std::ifstream file(path);
1515 if (!file.is_open()) {
1516 return load_failed(
"could not open file");
1519 const auto read_data_line = [&file](std::string &line) {
1520 while (std::getline(file, line)) {
1521 const std::size_t comment = line.find(
'#');
1522 if (comment != std::string::npos) {
1523 line.erase(comment);
1525 if (line.find_first_not_of(
" \t\r\n") != std::string::npos) {
1533 if (!read_data_line(line)) {
1534 return load_failed(
"missing model header");
1538 int vertex_count = 0;
1540 std::istringstream header(line);
1541 if (!(header >> name >> vertex_count >> poly_count) || vertex_count < 0 || poly_count < 0) {
1542 return load_failed(
"invalid model header");
1544 std::cout <<
"mxvk_math_eigen: PLG header parsed (object='" << name <<
"', vertices=" << vertex_count <<
", triangles=" << poly_count <<
")\n";
1546 std::vector<vec4D> loaded_local;
1547 std::vector<vec4D> loaded_trans;
1548 std::vector<vec2D> loaded_texcoords;
1549 std::vector<Triangle> loaded_vlist;
1550 loaded_local.reserve(
static_cast<std::size_t
>(vertex_count));
1551 loaded_trans.resize(
static_cast<std::size_t
>(vertex_count));
1552 loaded_texcoords.reserve(
static_cast<std::size_t
>(vertex_count));
1553 loaded_vlist.reserve(
static_cast<std::size_t
>(poly_count));
1555 for (
int i = 0; i < vertex_count; ++i) {
1556 if (!read_data_line(line)) {
1557 return load_failed(
"vertex data ended early");
1560 Eigen::Vector3f coordinates;
1561 std::istringstream vertex_line(line);
1562 if (!(vertex_line >> coordinates.x() >> coordinates.y() >> coordinates.z()) || !coordinates.allFinite()) {
1563 return load_failed(
"invalid vertex data");
1567 if (vertex_line >> texcoord.x) {
1568 if (!(vertex_line >> texcoord.y) || !std::isfinite(texcoord.x) || !std::isfinite(texcoord.y)) {
1569 return load_failed(
"invalid texture coordinates");
1573 const Eigen::Vector3f scaled =
1574 coordinates.cwiseProduct(Eigen::Vector3f(scale.x, scale.y, scale.z));
1575 if (!scaled.allFinite()) {
1576 return load_failed(
"scaled vertex is not finite");
1578 loaded_local.emplace_back(scaled.x(), scaled.y(), scaled.z(), 1.0f);
1579 loaded_texcoords.push_back(texcoord);
1582 for (
int i = 0; i < poly_count; ++i) {
1583 if (!read_data_line(line)) {
1584 return load_failed(
"triangle data ended early");
1589 std::istringstream polygon_line(line);
1590 if (!(polygon_line >> std::hex >> tri.state >> std::dec >> count) || count != 3 ||
1591 !(polygon_line >> tri.vert[0] >> tri.vert[1] >> tri.vert[2])) {
1592 return load_failed(
"invalid triangle data");
1594 for (
const int index : tri.vert) {
1595 if (index < 0 || index >= vertex_count) {
1596 return load_failed(
"triangle vertex index is out of range");
1599 loaded_vlist.push_back(tri);
1602 for (
auto &triangle : loaded_vlist) {
1609 local = std::move(loaded_local);
1610 trans = std::move(loaded_trans);
1611 texcoords = std::move(loaded_texcoords);
1612 vlist = std::move(loaded_vlist);
1616 std::cout <<
"mxvk_math_eigen: PLG model ready (object='" <<
object_name <<
"', average radius=" <<
avg_rad <<
", maximum radius=" <<
max_rad <<
")\n";
1622 return LoadPLG(path, scale, obj_pos, rotation);
1634 std::cout <<
"mxvk_math_eigen: loading OBJ model: " << path <<
'\n';
1638 std::cerr <<
"mxvk_math_eigen: failed to load OBJ model '" << path <<
"': " << error <<
'\n';
1642 std::unordered_map<std::string, int> material_indices;
1643 for (std::size_t index = 0; index < loaded.
materials.size(); ++index) {
1644 material_indices.emplace(loaded.
materials[index].name,
static_cast<int>(index));
1647 std::vector<vec4D> loaded_local;
1648 std::vector<vec2D> loaded_texcoords;
1649 std::vector<Triangle> loaded_vlist;
1650 loaded_local.reserve(loaded.
triangles.size() * 3);
1651 loaded_texcoords.reserve(loaded.
triangles.size() * 3);
1652 loaded_vlist.reserve(loaded.
triangles.size());
1658 triangle.source_object_name = source_triangle.
object_name;
1659 const auto material = material_indices.find(source_triangle.
material_name);
1660 if (material != material_indices.end()) {
1661 triangle.material_index = material->second;
1662 triangle.color = material_color(loaded.
materials[
static_cast<std::size_t
>(material->second)]);
1665 for (std::size_t vertex_index = 0; vertex_index < source_triangle.
vertices.size(); ++vertex_index) {
1667 const Eigen::Vector3f scaled =
1669 .cwiseProduct(Eigen::Vector3f(scale.x, scale.y, scale.z));
1670 if (!scaled.allFinite()) {
1671 std::cerr <<
"mxvk_math_eigen: failed to load OBJ model '" << path <<
"': scaled vertex is not finite\n";
1674 triangle.vert[vertex_index] =
static_cast<int>(loaded_local.size());
1675 loaded_local.emplace_back(scaled.x(), scaled.y(), scaled.z(), 1.0f);
1676 loaded_texcoords.emplace_back(source_vertex.
texcoord[0], source_vertex.
texcoord[1]);
1678 loaded_vlist.push_back(triangle);
1683 num_polys =
static_cast<int>(loaded_vlist.size());
1684 local = std::move(loaded_local);
1686 texcoords = std::move(loaded_texcoords);
1687 vlist = std::move(loaded_vlist);
1701 [[nodiscard]]
bool LoadMTL(
const std::string &path) {
1702 std::vector<OBJMaterial> loaded_materials;
1705 std::cerr <<
"mxvk_math_eigen: failed to load MTL file '" << path <<
"': " << error <<
'\n';
1709 std::unordered_map<std::string, int> material_indices;
1710 for (std::size_t index = 0; index < loaded_materials.size(); ++index) {
1711 material_indices.emplace(loaded_materials[index].name,
static_cast<int>(index));
1713 for (std::size_t index = 0; index <
vlist.size(); ++index) {
1714 const auto material = material_indices.find(
vlist[index].material_name);
1715 vlist[index].material_index = material == material_indices.end() ? -1 : material->second;
1716 if (material != material_indices.end()) {
1717 vlist[index].color = material_color(loaded_materials[
static_cast<std::size_t
>(material->second)]);
1721 materials = std::move(loaded_materials);
1730 for (std::size_t i = 0; i <
local.size(); ++i) {
1733 }
else if (type == 1) {
1734 for (
auto &vertex :
trans) {
1742 auto transform = [&mrot](std::vector<vec4D> &vertices) {
1743 for (
auto &vertex : vertices) {
1744 vertex = mrot.MulVec(vertex);
1749 }
else if (type == 1) {
1751 }
else if (type == 2) {
1753 for (std::size_t i = 0; i <
local.size(); ++i) {
1761 for (
auto &poly :
vlist) {
1765 if (poly.vert[0] < 0 || poly.vert[1] < 0 || poly.vert[2] < 0 ||
static_cast<std::size_t
>(poly.vert[0]) >=
trans.size() ||
static_cast<std::size_t
>(poly.vert[1]) >=
trans.size() ||
static_cast<std::size_t
>(poly.vert[2]) >=
trans.size()) {
1768 const vec4D u =
vec4D().
Build(
trans[
static_cast<std::size_t
>(poly.vert[0])],
trans[
static_cast<std::size_t
>(poly.vert[1])]);
1769 const vec4D v =
vec4D().
Build(
trans[
static_cast<std::size_t
>(poly.vert[0])],
trans[
static_cast<std::size_t
>(poly.vert[2])]);
1770 const vec4D n = u.CrossProduct(v);
1772 if (n.DotProduct(view) <= 0.0f) {
1780 for (
const auto &poly :
vlist) {
1782 for (
int i = 0; i < 3; ++i) {
1783 const auto index =
static_cast<std::size_t
>(poly.vert[i]);
1784 if (index <
local.size()) {
1785 tri.vlist[i] =
local[index];
1787 if (index <
trans.size()) {
1788 tri.tlist[i] =
trans[index];
1791 list.BuildRenderList(tri);
1797 for (
auto &poly :
vlist) {
1807 if (
local.empty()) {
1810 for (
const auto &vertex :
local) {
1811 const float dist = Eigen::Vector3f(vertex.x, vertex.y, vertex.z).norm();
1826 const auto channel = [](
float value) {
1827 return static_cast<MXCOLOR>(std::lround(std::clamp(value, 0.0f, 1.0f) * 255.0f));
1829 return (channel(material.
dissolve) << 24u) |
1830 (channel(material.
diffuse[0]) << 16u) |
1831 (channel(material.
diffuse[1]) << 8u) |
1919 mcam.LoadIdentity();
1920 mper.LoadIdentity();
1921 mscr.LoadIdentity();
1926 pos.Set(100.0f, 200.0f, 300.0f);
1927 dir.Set(-48.0f, 0.0f, 0.0f);
1932 void Init(
int camera_attr,
const vec4D &camera_pos,
const vec4D &camera_dir,
const vec4D *camera_target,
float near_z,
float far_z,
float fov_degrees,
float width,
float height) {
1936 target = camera_target !=
nullptr ? *camera_target :
vec4D();
1948 mcam.LoadIdentity();
1949 mper.LoadIdentity();
1950 mscr.LoadIdentity();
1955 Mat4D translation(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -
pos.x, -
pos.y, -
pos.z, 1);
1957 rotation.BuildXYZ(-
dir.x, -
dir.y, -
dir.z);
1958 mcam = translation * rotation;
1965 v.Set(0.0f, 1.0f, 0.0f);
1966 u =
v.CrossProduct(
n);
1968 v =
n.CrossProduct(
u);
1970 Mat4D translation(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -
pos.x, -
pos.y, -
pos.z, 1);
1971 Mat4D uvn(
u.x,
v.x,
n.x, 0,
u.y,
v.y,
n.y, 0,
u.z,
v.z,
n.z, 0, 0, 0, 0, 1);
1972 mcam = translation * uvn;
1977 for (
auto &poly : list.polys) {
1978 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1981 for (
auto &vertex : poly.tlist) {
1982 vertex =
mcam.MulVec(vertex);
1989 for (
auto &vertex :
object.trans) {
1990 vertex =
mcam.MulVec(vertex);
1996 for (
auto &poly : list.polys) {
1997 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2000 for (
auto &vertex : poly.tlist) {
2001 if (std::fabs(vertex.z) <=
EPSILON) {
2004 const Eigen::Vector2f projected =
2005 Eigen::Vector2f(vertex.x, vertex.y)
2008 vertex.x = projected.x();
2009 vertex.y = projected.y();
2016 for (
auto &vertex :
object.trans) {
2017 if (std::fabs(vertex.z) <=
EPSILON) {
2020 const Eigen::Vector2f projected =
2021 Eigen::Vector2f(vertex.x, vertex.y)
2024 vertex.x = projected.x();
2025 vertex.y = projected.y();
2033 for (
auto &poly : list.polys) {
2034 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2037 for (
auto &vertex : poly.tlist) {
2038 const Eigen::Vector2f screen =
2039 Eigen::Vector2f(alpha, beta) +
2040 Eigen::Vector2f(alpha, -beta)
2041 .cwiseProduct(Eigen::Vector2f(vertex.x, vertex.y));
2042 vertex.x = screen.x();
2043 vertex.y = screen.y();
2052 for (
auto &vertex :
object.trans) {
2053 const Eigen::Vector2f screen =
2054 Eigen::Vector2f(alpha, beta) +
2055 Eigen::Vector2f(alpha, -beta)
2056 .cwiseProduct(Eigen::Vector2f(vertex.x, vertex.y));
2057 vertex.x = screen.x();
2058 vertex.y = screen.y();
2064 struct SDLRendererPixelPlotter {
2074 SDL_RenderPoint(
renderer,
static_cast<float>(x),
static_cast<float>(y));
2079 struct VKSpritePixelPlotter {
2081 VK_Sprite *
sprite =
nullptr;
2089 sprite->drawSpriteRect(x, y, std::max(1,
size), std::max(1,
size));
2104 template <
typename PlotPixel>
2105 void draw_line(
int x0,
int y0,
int x1,
int y1,
MXCOLOR color, PlotPixel &&plot_pixel) {
2106 const int dx = std::abs(x1 - x0);
2107 const int sx = x0 < x1 ? 1 : -1;
2108 const int dy = -std::abs(y1 - y0);
2109 const int sy = y0 < y1 ? 1 : -1;
2113 plot_pixel(x0, y0, color);
2114 if (x0 == x1 && y0 == y1) {
2117 const int e2 = 2 * err;
2130 inline void draw_line(SDL_Renderer *renderer,
int x0,
int y0,
int x1,
int y1,
MXCOLOR color) {
2141 Eigen::Matrix2f edges;
2142 edges << p.x - a.x, b.x - a.x, p.y - a.y, b.y - a.y;
2143 return edges.determinant();
2155 template <
typename PlotPixel>
2158 if (std::fabs(area) <=
EPSILON) {
2162 const int min_x =
static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x})));
2163 const int max_x =
static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x})));
2164 const int min_y =
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y})));
2165 const int max_y =
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y})));
2167 for (
int y = min_y; y <= max_y; ++y) {
2168 for (
int x = min_x; x <= max_x; ++x) {
2169 const vec2D p(
static_cast<float>(x) + 0.5f,
static_cast<float>(y) + 0.5f);
2173 if ((area > 0.0f && w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) ||
2174 (area < 0.0f && w0 <= 0.0f && w1 <= 0.0f && w2 <= 0.0f)) {
2175 plot_pixel(x, y, color);
2202 template <
typename DrawSpan>
2214 [[nodiscard]]
float x_at(
int y)
const {
2215 return x0 +
static_cast<float>(y - y0) * slope;
2219 const int min_y = std::max(clip_min_y,
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y}))));
2220 const int max_y = std::min(clip_max_y,
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y}))));
2221 if (min_y > max_y) {
2225 std::array<SpanEdge, 3> edges{};
2227 const auto add_edge = [&](
vec2D a,
vec2D b) {
2228 if (std::fabs(a.y - b.y) <=
EPSILON) {
2235 const int y0 = std::max(min_y,
static_cast<int>(std::ceil(a.y - 0.5f)));
2236 const int y1 = std::min(max_y,
static_cast<int>(std::ceil(b.y - 0.5f)) - 1);
2241 SpanEdge &edge = edges[
static_cast<std::size_t
>(edge_count++)];
2244 edge.slope = (b.x - a.x) / (b.y - a.y);
2245 edge.x0 = a.x + ((
static_cast<float>(y0) + 0.5f) - a.y) * edge.slope;
2252 for (
int y = min_y; y <= max_y; ++y) {
2253 std::array<float, 3> intersections{};
2254 int intersection_count = 0;
2256 for (
int edge_index = 0; edge_index < edge_count; ++edge_index) {
2257 const SpanEdge &edge = edges[
static_cast<std::size_t
>(edge_index)];
2258 if (y >= edge.y0 && y <= edge.y1) {
2259 intersections[
static_cast<std::size_t
>(intersection_count++)] = edge.x_at(y);
2263 if (intersection_count < 2) {
2267 float min_x = intersections[0];
2268 float max_x = intersections[0];
2269 for (
int i = 1; i < intersection_count; ++i) {
2270 min_x = std::min(min_x, intersections[
static_cast<std::size_t
>(i)]);
2271 max_x = std::max(max_x, intersections[
static_cast<std::size_t
>(i)]);
2274 const int x0 =
static_cast<int>(std::ceil(min_x - 0.5f));
2275 const int x1 =
static_cast<int>(std::floor(max_x - 0.5f));
2277 draw_span(x0, x1, y, color);
2282 template <
typename DrawSpan>
2333 void Begin(
int width,
int height, std::function<
void(
int,
int,
MXCOLOR)> plotter) {
2334 plot_pixel = std::move(plotter);
2335 plot_span = [
this](
int x0,
int x1,
int y,
MXCOLOR color) {
2336 for (
int x = x0; x <= x1; ++x) {
2337 plot_pixel(x, y, color);
2344 [[maybe_unused]]
static const bool PIPELINE_LOGGED = [width, height] {
2345 std::cout <<
"mxvk_math_eigen: software raster pipeline ready (" << width <<
'x' << height <<
")\n";
2351 void Begin(SDL_Renderer *renderer,
int width,
int height) {
2353 plot_span = [renderer](
int x0,
int x1,
int y,
MXCOLOR color) {
2354 if (renderer ==
nullptr) {
2358 SDL_RenderLine(renderer,
static_cast<float>(x0),
static_cast<float>(y),
static_cast<float>(x1),
static_cast<float>(y));
2365 plot_span = [&sprite, pixel_size](
int x0,
int x1,
int y,
MXCOLOR) {
2366 const int size = std::max(1, pixel_size);
2395 bool ClipLine(
int &x0,
int &y0,
int &x1,
int &y1)
const {
2398 const auto interpolate = [](
int dependent0,
int dependent1,
int independent0,
int independent1,
int boundary) {
2399 const double value =
2400 static_cast<double>(dependent0) +
2401 (
static_cast<double>(dependent1) -
static_cast<double>(dependent0)) *
2402 (
static_cast<double>(boundary) -
static_cast<double>(independent0)) /
2403 (
static_cast<double>(independent1) -
static_cast<double>(independent0));
2404 return static_cast<int>(std::lround(value));
2408 if ((code0 | code1) == 0) {
2411 if ((code0 & code1) != 0) {
2415 const int out_code = code0 != 0 ? code0 : code1;
2419 if ((out_code &
CODE_N) != 0) {
2425 }
else if ((out_code &
CODE_S) != 0) {
2431 }
else if ((out_code &
CODE_E) != 0) {
2445 if (out_code == code0) {
2459 if (plot_pixel &&
ClipLine(x0, y0, x1, y1)) {
2460 draw_line(x0, y0, x1, y1, color, plot_pixel);
2480 !std::isfinite(first.x) ||
2481 !std::isfinite(first.y) ||
2482 !std::isfinite(first.z) ||
2483 !std::isfinite(second.x) ||
2484 !std::isfinite(second.y) ||
2485 !std::isfinite(second.z)) {
2489 const int framebuffer_width =
max_clip_x + 1;
2490 const int framebuffer_height =
max_clip_y + 1;
2491 const std::size_t required_depth_values =
2492 static_cast<std::size_t
>(framebuffer_width) *
static_cast<std::size_t
>(framebuffer_height);
2493 if (framebuffer_width <= 0 ||
2494 framebuffer_height <= 0 ||
2495 depth_buffer.size() < required_depth_values) {
2499 const float delta_x = second.x - first.x;
2500 const float delta_y = second.y - first.y;
2501 float first_fraction = 0.0f;
2502 float last_fraction = 1.0f;
2503 const auto clip_fraction = [&first_fraction, &last_fraction](
float direction,
float distance) {
2504 if (std::abs(direction) <=
EPSILON) {
2505 return distance >= 0.0f;
2508 const float fraction = distance / direction;
2509 if (direction < 0.0f) {
2510 first_fraction = std::max(first_fraction, fraction);
2512 last_fraction = std::min(last_fraction, fraction);
2514 return first_fraction <= last_fraction;
2517 if (!clip_fraction(-delta_x, first.x -
static_cast<float>(
clip_min_x)) ||
2518 !clip_fraction(delta_x,
static_cast<float>(
clip_max_x) - first.x) ||
2519 !clip_fraction(-delta_y, first.y -
static_cast<float>(
clip_min_y)) ||
2520 !clip_fraction(delta_y,
static_cast<float>(
clip_max_y) - first.y)) {
2524 const float clipped_delta_x = delta_x * (last_fraction - first_fraction);
2525 const float clipped_delta_y = delta_y * (last_fraction - first_fraction);
2526 const float step_count = std::ceil(std::max(std::abs(clipped_delta_x), std::abs(clipped_delta_y)));
2527 if (step_count >
static_cast<float>(std::numeric_limits<int>::max())) {
2530 const int steps = std::max(1,
static_cast<int>(step_count));
2531 const float first_reciprocal_depth = 1.0f / first.z;
2532 const float second_reciprocal_depth = 1.0f / second.z;
2534 for (
int step = 0; step <= steps; ++step) {
2535 const float clipped_fraction =
static_cast<float>(step) /
static_cast<float>(steps);
2536 const float fraction =
2538 (last_fraction - first_fraction) * clipped_fraction;
2539 const int x =
static_cast<int>(std::lround(first.x + delta_x * fraction));
2540 const int y =
static_cast<int>(std::lround(first.y + delta_y * fraction));
2545 const float reciprocal_depth =
2546 first_reciprocal_depth +
2547 (second_reciprocal_depth - first_reciprocal_depth) * fraction;
2548 if (reciprocal_depth <=
EPSILON) {
2552 const float depth = 1.0f / reciprocal_depth;
2553 const std::size_t pixel_index =
2554 static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(framebuffer_width) +
2555 static_cast<std::size_t
>(x);
2556 if (depth >= depth_buffer[pixel_index]) {
2560 depth_buffer[pixel_index] = depth;
2561 plot_pixel(x, y, color);
2568 static_cast<int>(std::lround(first.x)),
2569 static_cast<int>(std::lround(first.y)),
2570 static_cast<int>(std::lround(second.x)),
2571 static_cast<int>(std::lround(second.y)),
2574 static_cast<int>(std::lround(second.x)),
2575 static_cast<int>(std::lround(second.y)),
2576 static_cast<int>(std::lround(third.x)),
2577 static_cast<int>(std::lround(third.y)),
2580 static_cast<int>(std::lround(third.x)),
2581 static_cast<int>(std::lround(third.y)),
2582 static_cast<int>(std::lround(first.x)),
2583 static_cast<int>(std::lround(first.y)),
2600 const int min_x =
static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x})));
2601 const int max_x =
static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x})));
2602 const int min_y =
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y})));
2603 const int max_y =
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y})));
2615 plot_span(x0, x1, y, pixel_color);
2627 for (
const auto &poly : list.polys) {
2628 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2637 for (
const auto &poly : list.polys) {
2638 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2647 for (
const auto &poly : list.polys) {
2648 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2660 for (
const auto &poly :
object.vlist) {
2661 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2664 const auto a =
static_cast<std::size_t
>(poly.vert[0]);
2665 const auto b =
static_cast<std::size_t
>(poly.vert[1]);
2666 const auto c =
static_cast<std::size_t
>(poly.vert[2]);
2667 if (a >=
object.trans.size() || b >=
object.trans.size() || c >=
object.trans.size()) {
2679 for (
const auto &poly :
object.vlist) {
2680 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2683 const auto a =
static_cast<std::size_t
>(poly.vert[0]);
2684 const auto b =
static_cast<std::size_t
>(poly.vert[1]);
2685 const auto c =
static_cast<std::size_t
>(poly.vert[2]);
2686 if (a >=
object.trans.size() || b >=
object.trans.size() || c >=
object.trans.size()) {
2695 plot_pixel =
nullptr;
2696 plot_span =
nullptr;
2700 std::function<void(
int,
int,
MXCOLOR)> plot_pixel;
2701 std::function<void(
int,
int,
int,
MXCOLOR)> plot_span;
Camera and projection data for the simple software 3D pipeline.
float viewport_width
Viewport width in pixels.
int state
Camera state flags.
float aspect_ratio
Viewport aspect ratio.
float far_clip_z
Far clipping plane Z.
int attr
Camera attributes.
vec4D target
Camera look-at target.
float view_dist
Distance from camera to view plane.
float near_clip_z
Near clipping plane Z.
void PerspectiveToScreen(RenderList &list) const
Convert a render list from perspective coordinates to screen coordinates.
Plane3D lt_clip_plane
Left clipping plane.
void PerspectiveToScreen(mxObject &object) const
Convert an object's transformed vertices from perspective coordinates to screen coordinates.
vec4D dir
Euler camera direction in degrees.
vec4D pos
Camera position.
float viewport_height
Viewport height in pixels.
float viewplane_width
View-plane width.
void InitalizeForEuler()
Initialize camera fields for the Euler example path.
void CameraToPerspective(mxObject &object) const
Project an object's transformed vertices from camera space to perspective space.
float viewport_center_y
Viewport center Y coordinate.
Mat4D mcam
World-to-camera transform matrix.
Plane3D bt_clip_plane
Bottom clipping plane.
float viewplane_height
View-plane height.
Mat4D mscr
Screen transform matrix placeholder.
void BuildUVN(int)
Build the world-to-camera matrix from the camera position and look-at target.
void WorldToCamera(RenderList &list) const
Transform a render list from world space to camera space.
vec4D u
UVN camera U basis vector.
void BuildEuler(int)
Build the world-to-camera matrix from Euler direction angles.
vec4D v
UVN camera V basis vector.
void Init(int camera_attr, const vec4D &camera_pos, const vec4D &camera_dir, const vec4D *camera_target, float near_z, float far_z, float fov_degrees, float width, float height)
Initialize camera projection, viewport, position, and direction parameters.
Plane3D tp_clip_plane
Top clipping plane.
Plane3D rt_clip_plane
Right clipping plane.
Mat4D mper
Perspective transform matrix placeholder.
Camera()
Construct a camera with identity matrices.
float fov
Vertical field of view in degrees.
void WorldToCamera(mxObject &object) const
Transform an object's transformed vertices from world space to camera space.
float viewport_center_x
Viewport center X coordinate.
void CameraToPerspective(RenderList &list) const
Project a render list from camera space to perspective space.
vec4D n
UVN camera N basis vector.
Two-element column-vector storage used by 2x2 linear solves.
constexpr Mat1D()=default
Construct a zero-initialized 2-element vector.
float mat[2]
Matrix/vector elements.
void Set(float m0, float m1)
Set both elements.
constexpr Mat1D(float m0, float m1)
Construct from explicit elements.
Three-element column-vector storage used by 3x3 linear solves.
float mat[3]
Matrix/vector elements.
constexpr Mat1x3D(float m0, float m1, float m2)
Construct from explicit elements.
constexpr Mat1x3D()=default
Construct a zero-initialized 3-element vector.
constexpr Mat1x4D(float m0, float m1, float m2, float m3)
Construct from explicit elements.
float mat[4]
Matrix/vector elements.
constexpr Mat1x4D()=default
Construct a zero-initialized 4-element vector.
Two-by-two matrix with arithmetic, determinant, inverse, and solve helpers.
Mat2D()=default
Construct a zero-initialized matrix.
Mat2D operator*(const Mat2D &m) const
Multiply two 2x2 matrices.
bool Inverse(Mat2D &out) const
Compute the inverse matrix.
float Determinate() const
Compute the matrix determinant.
Mat2D operator-(const Mat2D &m) const
Subtract two matrices component-wise.
static bool Solve2x2(const Mat2D &a, Mat1D &out, const Mat1D &b)
Solve a 2x2 linear system.
void LoadIdentity()
Set this matrix to the identity matrix.
Mat2D(float m00, float m01, float m10, float m11)
Construct from explicit row-major elements.
Mat2D operator+(const Mat2D &m) const
Add two matrices component-wise.
float mat[2][2]
Matrix elements indexed as row, column.
void Set(float m00, float m01, float m10, float m11)
Set all matrix elements in row-major order.
Three-by-three matrix with multiplication, vector transform, inverse, and solve helpers.
float Determinate() const
Compute the matrix determinant.
vec3D MulVec(const vec3D &in) const
Transform a 3D vector by this matrix.
Mat3D operator*(const Mat3D &m) const
Multiply two 3x3 matrices.
Mat3D()=default
Construct a zero-initialized matrix.
static bool Solve3x3(const Mat3D &a, Mat1x3D &out, const Mat1x3D &b)
Solve a 3x3 linear system.
Mat3D(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22)
Construct from explicit row-major elements.
float mat[3][3]
Matrix elements indexed as row, column.
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.
void LoadIdentity()
Set this matrix to the identity matrix.
void MulVec(const vec3D &in, vec3D &out) const
Transform a 3D vector and write the result to out.
bool Inverse(Mat3D &out) const
Compute the inverse matrix.
Four-by-four homogeneous transform matrix.
float mat[4][4]
Matrix elements indexed as row, column.
bool Inverse(Mat4D &out) const
Compute the inverse matrix.
void BuildXYZ(float theta_x, float theta_y, float theta_z)
Build an XYZ Euler rotation matrix from angles in degrees.
Mat4D()=default
Construct a zero-initialized matrix.
Mat4D & operator*=(const Mat4D &m)
Multiply this matrix by another matrix in place.
void MulVec(const vec3D &in, vec3D &out) const
Transform a 3D point and write the result to out.
vec4D MulVec(const vec4D &in) const
Transform a homogeneous 4D vector by this matrix.
vec3D MulVec(const vec3D &in) const
Transform a 3D point by this matrix using W = 1.
void MulVec(std::span< const vec4D > input, std::span< vec4D > output) const
Transform a batch of homogeneous 4D vectors.
void Set(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33)
Set all matrix elements in row-major order.
void MulVec(const vec4D &in, vec4D &out) const
Transform a homogeneous 4D vector and write the result to out.
void LoadIdentity()
Set this matrix to the identity matrix.
Mat4D operator+(const Mat4D &m) const
Add two matrices component-wise.
Mat4D operator*(const Mat4D &m) const
Multiply two 4x4 matrices.
Mat4D(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33)
Construct from explicit row-major elements.
float mat[4][3]
Matrix elements indexed as row, column.
Clipped software rasterization pipeline for lines and filled triangles.
void End()
End rendering and release the current plotting callbacks.
void DrawFilledTriangle(const vec2D &p0, const vec2D &p1, const vec2D &p2, MXCOLOR color) const
Draw a clipped filled triangle from 2D screen-space vertices.
int min_clip_x
Minimum clip X coordinate.
void DrawFilledTriangle(const vec4D &p0, const vec4D &p1, const vec4D &p2, MXCOLOR color) const
Draw a clipped filled triangle from homogeneous screen-space vertices.
int min_clip_y
Minimum clip Y coordinate.
void DrawSolidPolys(const RenderList &list) const
Draw active render-list triangles as filled polygons.
void Begin(VK_Sprite &sprite, int width, int height, int pixel_size=1)
Begin rendering to a VK_Sprite with optional square pixel size.
void DrawObject(const mxObject &object) const
Draw an object's active transformed triangles as clipped wireframes.
void DrawClipedLine(int x0, int y0, int x1, int y1, MXCOLOR color) const
Draw a clipped line. Kept with the original misspelled name for compatibility.
void DrawPolys(const RenderList &list) const
Draw active render-list triangles as clipped wireframes.
int clip_min_x
Active minimum clip X coordinate.
int clip_max_x
Active maximum clip X coordinate.
void DrawClippedLine(int x0, int y0, int x1, int y1, MXCOLOR color) const
Draw a clipped line.
int max_clip_y
Maximum clip Y coordinate.
void DrawObject(const mxObject &object, std::span< float > depth_buffer) const
Draw an object's active transformed triangles as depth-tested clipped wireframes.
void DrawPolys(const RenderList &list, std::span< float > depth_buffer) const
Draw active render-list triangles as depth-tested clipped wireframes.
void DrawWireframeTriangle(const vec4D &first, const vec4D &second, const vec4D &third, MXCOLOR color, std::span< float > depth_buffer) const
Draw a clipped wireframe triangle with perspective-correct depth testing.
void DrawDepthTestedLine(const vec4D &first, const vec4D &second, MXCOLOR color, std::span< float > depth_buffer) const
Draw a clipped screen-space line with perspective-correct depth testing.
bool ClipLine(int &x0, int &y0, int &x1, int &y1) const
Clip a line segment to the active clip rectangle.
LINE_CODE
Cohen-Sutherland region codes for line clipping.
@ CODE_C
Center/inside code.
@ CODE_S
South/bottom code.
void Begin(int width, int height, std::function< void(int, int, MXCOLOR)> plotter)
Begin rendering with a custom pixel plotter.
void Begin(SDL_Renderer *renderer, int width, int height)
Begin rendering to an SDL renderer.
int clip_min_y
Active minimum clip Y coordinate.
int ComputeCode(int x, int y) const
Compute the Cohen-Sutherland region code for a point.
int max_clip_x
Maximum clip X coordinate.
void DrawWireframeTriangle(const vec4D &first, const vec4D &second, const vec4D &third, MXCOLOR color) const
Draw a clipped wireframe triangle without depth testing.
int clip_max_y
Active maximum clip Y coordinate.
Quaternion used for 3D rotations.
void Inverse()
Invert this quaternion in place.
void InverseNormal()
Invert a unit quaternion by conjugating it.
constexpr QuatType()
Construct the identity quaternion.
float Norm2() const
Compute the squared norm.
constexpr QuatType operator*(const QuatType &q) const
Multiply two quaternions.
float z
Z component of the vector part.
constexpr QuatType operator+(const QuatType &q) const
Add two quaternions component-wise.
constexpr QuatType operator-(const QuatType &q) const
Subtract two quaternions component-wise.
float Norm() const
Compute the norm.
void Scale(float f)
Scale all quaternion components in place.
void vec4DthetaQuat(float theta_degrees, const vec4D &axis)
Build a quaternion from a 4D axis vector and angle in degrees.
void vec3DthetaQuat(float theta_degrees, const vec3D &axis)
Build a quaternion from an axis and angle in degrees.
float x
X component of the vector part.
void EulerZYX(float theta_x, float theta_y, float theta_z)
Build a quaternion from Euler angles in ZYX order, with angles in degrees.
void Normalize()
Normalize this quaternion in place, or reset it to identity if it is too small.
void Conj()
Conjugate this quaternion in place.
constexpr QuatType(float x_value, float y_value, float z_value, float w_value)
Construct from explicit quaternion components.
QuatType TripleProduct(const QuatType &p1, const QuatType &p2) const
Return the quaternion product (*this * p1) * p2.
void QuatToVec3D(float *theta_degrees, vec3D &axis) const
Convert this quaternion to axis-angle form.
float y
Y component of the vector part.
QuatType & operator*=(const QuatType &q)
Multiply this quaternion by another quaternion in place.
Flat list of triangles prepared for transformation and rasterization.
void RemoveFaces(const vec4D &pos)
Mark back-facing triangles relative to a view position.
int num_polys
Cached polygon count matching polys.size().
void ModelToWorld(const vec4D &pos, int type)
Translate model-space vertices into world-space transformed vertices.
std::vector< Triangle > polys
Triangle storage.
void TransformRenderList(const Mat4D &mrot, int type)
Transform active non-backface triangles by a matrix.
void Reset()
Clear all triangles from the render list.
void BuildRenderList(const Triangle &triangle)
Append one triangle to the render list.
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.
Simple mesh object loaded from PLG-style indexed triangle data.
void SetState(int new_state)
Replace the object state flags.
bool LoadMX(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation)
Load an MX mesh file using the PLG loader compatibility path.
bool LoadMTL(const std::string &path)
Replace this object's material library with an MTL file.
float ComputeRad()
Compute and cache average and maximum local-space radii.
float max_rad
Maximum radius from the local origin.
vec4D world_pos
Object world position.
std::vector< vec2D > texcoords
Optional texture coordinates corresponding to local-space vertices.
float avg_rad
Average radius from the local origin.
std::string material_library_path
Resolved path of the OBJ material library.
vec4D ux
Local X basis vector.
bool LoadOBJ(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation)
Load a Wavefront OBJ mesh and its referenced MTL material library.
int num_polys
Number of loaded polygons.
std::vector< OBJMaterial > materials
Materials loaded from the OBJ material library.
int attr
Object attributes.
void BuildRenderList(RenderList &list) const
Append this object's triangles to a render list.
std::string object_name
Object name loaded from the model file.
vec4D dir
Object direction.
void Reset()
Reset object and polygon state to active.
int num_vertices
Number of loaded vertices.
int state
Object state flags.
std::vector< vec4D > local
Local-space vertices.
std::vector< Triangle > vlist
Indexed triangle list.
vec4D uy
Local Y basis vector.
void RemoveFaces(const vec4D &pos)
Mark object polygons that face away from a view position.
std::vector< vec4D > trans
Transformed vertices.
void ModelToWorld(int type=0)
Convert local or transformed vertices to world space.
void TransformObject(const Mat4D &mrot, int type=0)
Apply a transform to local vertices, transformed vertices, or local-to-transformed output.
vec4D uz
Local Z basis vector.
bool LoadPLG(const std::string &path, const vec4D &scale, const vec4D &obj_pos, const vec4D &rotation)
Load a PLG mesh file.
Two-dimensional float vector with common arithmetic helpers.
float Length() const
Compute the Euclidean length of this vector.
constexpr vec2D()
Construct the zero vector.
float DotProduct(const vec2D &v) const
Compute the dot product with another vector.
constexpr float DotProduct(const vec2D &v) const
Compute the dot product with another vector.
void Normalize(vec2D &v) const
Write a normalized copy of this vector to v.
void Set(float x_value, float y_value)
Set both vector coordinates.
void Normalize()
Normalize this vector in place, or reset it to zero if it is too short.
vec2D & operator+=(const vec2D &v)
Add another vector to this vector.
vec2D & operator-=(const vec2D &v)
Subtract another vector from this vector.
vec2D operator+(const vec2D &v) const
Add two vectors component-wise.
float Cos(const vec2D &v) const
Compute the cosine of the angle between this vector and another vector.
vec2D operator*(float k) const
Scale this vector by a scalar.
vec2D Scale(float k) const
Return a scaled copy of this vector.
std::string Print(const std::string &name="v") const
Format this vector as a named angle-bracket tuple.
constexpr vec2D(float x_value, float y_value)
Construct a vector from explicit coordinates.
vec2D operator-(const vec2D &v) const
Subtract two vectors component-wise.
vec2D & operator=(const vec2D &)=default
void ScaleThis(float k)
Scale this vector in place.
Three-dimensional float vector with arithmetic, dot, and cross-product helpers.
vec3D operator-(const vec3D &v) const
Subtract two vectors component-wise.
vec3D operator+(const vec3D &v) const
Add two vectors component-wise.
constexpr vec3D(float x_value, float y_value, float z_value)
Construct a vector from explicit coordinates.
float DotProduct(const vec3D &v) const
Compute the dot product with another vector.
vec3D Scale(float k) const
Return a scaled copy of this vector.
constexpr float DotProduct(const vec3D &v) const
Compute the dot product with another vector.
vec3D & operator=(const vec3D &)=default
vec3D & operator-=(const vec3D &v)
Subtract another vector from this vector.
constexpr vec3D()
Construct the zero vector.
vec3D CrossProduct(const vec3D &v) const
Compute the right-handed cross product with another vector.
float Cos(const vec3D &v) const
Compute the cosine of the angle between this vector and another vector.
void Set(float x_value, float y_value, float z_value)
Set all vector coordinates.
vec3D & operator+=(const vec3D &v)
Add another vector to this vector.
vec3D operator*(float k) const
Scale this vector by a scalar.
float Length() const
Compute the Euclidean length of this vector.
void Normalize(vec3D &v) const
Write a normalized copy of this vector to v.
void Normalize()
Normalize this vector in place, or reset it to zero if it is too short.
void ScaleThis(float k)
Scale this vector in place.
std::string Print(const std::string &name="v") const
Format this vector as a named angle-bracket tuple.
Four-dimensional float vector used for homogeneous 3D coordinates.
constexpr vec4D()
Construct the homogeneous origin.
float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
float Length() const
Compute the 3D Euclidean length, ignoring the W component.
float Cos(const vec4D &v) const
Compute the cosine of the angle between the 3D components of two vectors.
float w
Homogeneous W coordinate.
vec4D operator*(float k) const
Scale this vector by a scalar.
void Normalize()
Normalize the 3D components in place while preserving W.
vec4D CrossProduct(const vec4D &v) const
Compute the 3D cross product and return it as a direction with W set to 0.
constexpr vec4D(float x_value, float y_value, float z_value, float w_value=1.0f)
Construct a homogeneous vector from explicit coordinates.
vec4D operator-(const vec4D &v) const
Subtract two vectors component-wise.
vec4D & operator-=(const vec4D &v)
Subtract another vector from this vector.
constexpr float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
vec4D operator*(const vec4D &v) const
Multiply two vectors component-wise.
void Normalize(vec4D &v) const
Write a normalized copy of this vector to v.
vec4D Build(const vec4D &from, const vec4D &to) const
Build a direction vector from from to to with W set to 0.
void Build(const vec4D &to)
Replace this vector with the direction from this point to to.
vec4D & operator+=(const vec4D &v)
Add another vector to this vector.
vec4D operator+(const vec4D &v) const
Add two vectors component-wise.
std::string Print(const std::string &name="v") const
Format this vector as a named angle-bracket tuple.
void Set(const vec4D &v)
Copy coordinates from another vector.
vec4D Scale(float k) const
Return a scaled copy of this vector.
void Set(float x_value, float y_value, float z_value, float w_value=1.0f)
Set all vector coordinates.
vec4D & operator=(const vec4D &)=default
void ScaleThis(float k)
Scale this vector in place.
Vulkan 2-D sprite renderer with optional custom shaders and instancing.
bool load_mtl_file(const std::string &path, std::vector< OBJMaterial > &materials, std::string &error)
bool load_obj_file(const std::string &path, OBJLoadResult &result, std::string &error)
Utilities for loading and saving PNG images.
int rrand(int x, int y)
Return a pseudo-random integer in the inclusive range between two bounds.
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
std::array< float, 361 > cos_look
Cosine lookup table with one entry per degree from 0 through 360.
void BuildTables()
Rebuild the sine and cosine lookup tables.
MXCOLOR shade_color(MXCOLOR color, float intensity)
Scale the RGB channels of a color while preserving alpha.
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
float edge_function(const vec2D &a, const vec2D &b, const vec2D &p)
Compute the signed edge function value for point p relative to edge a-b.
std::array< float, 361 > build_cos_table()
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.
std::ostream & operator<<(std::ostream &out, const vec2D &v)
Write a 2D vector to a stream using vec2D::Print().
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
float deg2rad(float ang)
Convert degrees to radians.
float fast_cosf(float theta_degrees)
Approximate cosine using the degree lookup table with linear interpolation.
void draw_filled_triangle_spans(vec2D p0, vec2D p1, vec2D p2, MXCOLOR color, DrawSpan &&draw_span)
@ MX_ACTIVE
Active object or polygon.
@ MX_CULLED
Object or polygon is culled.
@ MX_BACKFACE
Polygon is marked as a backface.
@ MX_VISIBLE
Visible object or polygon.
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
constexpr float PI
Mathematical constant pi as a single-precision value.
std::array< float, 361 > sin_look
Sine lookup table with one entry per degree from 0 through 360.
float fast_sinf(float theta_degrees)
Approximate sine using the degree lookup table with linear interpolation.
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.
std::istream & operator>>(std::istream &in, vec2D &v)
Read a 2D vector from a stream as two scalar coordinates.
void draw_line(int x0, int y0, int x1, int y1, MXCOLOR color, PlotPixel &&plot_pixel)
Draw a line with Bresenham-style integer stepping.
float rad2deg(float rad)
Convert radians to degrees.
std::array< float, 361 > build_sin_table()
float z
Height coordinate.
float theta
Azimuth angle in degrees for this framework's math helpers.
Wavefront material data used by the software rasterizer.
std::array< float, 3 > diffuse
void Set(const vec3D &point, const vec3D &normal, bool normalize)
Set the plane point and normal, optionally normalizing the normal.
Plane3D()=default
Construct an uninitialized plane.
vec3D p0
Point on the plane.
Plane3D(const vec3D &point, const vec3D &normal)
Construct from a point and normal vector.
float theta
Angle in degrees for this framework's math helpers.
Pixel plotter adapter that writes packed MXVK colors to an SDL renderer.
void operator()(int x, int y, MXCOLOR color) const
Plot one pixel if the renderer is valid.
SDL_Renderer * renderer
SDL renderer receiving plotted pixels.
float theta
Azimuth angle in degrees for this framework's math helpers.
float phi
Inclination angle in degrees for this framework's math helpers.
Triangle primitive used by the simple software rendering pipeline.
std::string material_name
Wavefront material name retained for later MTL replacement.
int attr
Application-defined polygon attributes.
std::string source_object_name
Wavefront object name from the o section containing this triangle.
vec4D tlist[3]
Transformed vertex positions.
int material_index
Index of the OBJ/MTL material used by this triangle, or -1.
MXCOLOR color
Triangle color.
int state
Polygon state flags.
vec4D vlist[3]
Working vertex positions.
int vert[3]
Indices into an object's vertex arrays.
Pixel plotter adapter that draws square pixels into a VK_Sprite.
void operator()(int x, int y, MXCOLOR) const
Plot one square pixel if the sprite is valid.
int size
Square pixel size in sprite coordinates.
VK_Sprite * sprite
Sprite receiving plotted pixels.
std::vector< OBJMaterial > materials
std::string material_library_path
std::vector< OBJTriangle > triangles
std::array< OBJVertex, 3 > vertices
std::string material_name
std::array< float, 3 > position
std::array< float, 2 > texcoord
Parametric 2D line segment represented by a start point, end point, and direction.
paramLine2D(const vec2D &start, const vec2D &end, const vec2D &dir)
Construct from explicit endpoints and direction.
paramLine2D()=default
Construct an uninitialized line segment.
vec2D ComputePoint(float t, vec2D &out) const
Compute the point p0 + v * t and write it to out.
void Set(const vec2D &start, const vec2D &end, const vec2D &dir)
Set explicit endpoints and direction.
int Intersect(const paramLine2D &line, vec2D &out) const
Intersect this segment with another segment and compute the point.
vec2D p0
Segment start point.
int Intersect(const paramLine2D &line, float &t_this, float &t_other) const
Intersect this segment with another parametric segment.
void Init(const vec2D &start, const vec2D &end)
Initialize from endpoints and derive the direction vector.
vec2D p1
Segment end point.
vec2D ComputePoint(float t) const
Compute the point p0 + v * t.
vec2D v
Direction vector, commonly p1 - p0.
vec3D ComputePoint(float t) const
Compute the point p0 + v * t.
vec3D ComputePoint(float t, vec3D &out) const
Compute the point p0 + v * t and write it to out.
paramLine3D()=default
Construct an uninitialized line segment.
vec3D p1
Segment end point.
paramLine3D(const vec3D &start, const vec3D &end, const vec3D &dir)
Construct from explicit endpoints and direction.
vec3D p0
Segment start point.
void Set(const vec3D &start, const vec3D &end, const vec3D &dir)
Set explicit endpoints and direction.
vec3D v
Direction vector, commonly p1 - p0.
void Init(const vec3D &start, const vec3D &end)
Initialize from endpoints and derive the direction vector.