24#include <unordered_map>
36 inline constexpr float PI = 3.14159265358979323846f;
39 inline constexpr float EPSILON = 1.0e-5f;
51 [[nodiscard]]
inline constexpr MXCOLOR MXVK_RGB(
int r,
int g,
int b) {
return 0xFF000000u | ((
static_cast<MXCOLOR>(r) & 0xFFu) << 16u) | ((
static_cast<MXCOLOR>(g) & 0xFFu) << 8u) | (
static_cast<MXCOLOR>(b) & 0xFFu); }
54 [[nodiscard]]
inline constexpr std::uint8_t
color_r(
MXCOLOR color) {
return static_cast<std::uint8_t
>((color >> 16u) & 0xFFu); }
57 [[nodiscard]]
inline constexpr std::uint8_t
color_g(
MXCOLOR color) {
return static_cast<std::uint8_t
>((color >> 8u) & 0xFFu); }
60 [[nodiscard]]
inline constexpr std::uint8_t
color_b(
MXCOLOR color) {
return static_cast<std::uint8_t
>(color & 0xFFu); }
63 [[nodiscard]]
inline constexpr std::uint8_t
color_a(
MXCOLOR color) {
return static_cast<std::uint8_t
>((color >> 24u) & 0xFFu); }
72 intensity = std::clamp(intensity, 0.0f, 1.0f);
73 const auto scale = [intensity](std::uint8_t component) {
return static_cast<int>(std::clamp(
static_cast<float>(component) * intensity, 0.0f, 255.0f)); };
78 std::array<float, 361> values{};
79 for (
int ang = 0; ang <= 360; ++ang) {
80 values[
static_cast<std::size_t
>(ang)] = std::sin(
static_cast<float>(ang) *
PI / 180.0f);
86 std::array<float, 361> values{};
87 for (
int ang = 0; ang <= 360; ++ang) {
88 values[
static_cast<std::size_t
>(ang)] = std::cos(
static_cast<float>(ang) *
PI / 180.0f);
101 std::cout <<
"mxvk_math_eigen: building trigonometric lookup tables\n";
102 for (
int ang = 0; ang <= 360; ++ang) {
103 const float theta =
static_cast<float>(ang) *
PI / 180.0f;
104 cos_look[
static_cast<std::size_t
>(ang)] = std::cos(theta);
105 sin_look[
static_cast<std::size_t
>(ang)] = std::sin(theta);
107 std::cout <<
"mxvk_math_eigen: trigonometric lookup tables ready (361 entries)\n";
111 [[nodiscard]]
inline float deg2rad(
float ang) {
return ang *
PI / 180.0f; }
114 [[nodiscard]]
inline float rad2deg(
float rad) {
return rad * 180.0f /
PI; }
121 [[nodiscard]]
inline float fast_cosf(
float theta_degrees) {
122 if (!std::isfinite(theta_degrees)) {
123 return std::numeric_limits<float>::quiet_NaN();
125 theta_degrees = std::fmod(theta_degrees, 360.0f);
126 if (theta_degrees < 0.0f) {
127 theta_degrees += 360.0f;
129 if (theta_degrees >= 360.0f) {
130 theta_degrees = 0.0f;
132 const int theta_int =
static_cast<int>(theta_degrees);
133 const int next_index = (theta_int + 1) % 360;
134 const float theta_frac = theta_degrees -
static_cast<float>(theta_int);
135 return cos_look[
static_cast<std::size_t
>(theta_int)] + theta_frac * (
cos_look[
static_cast<std::size_t
>(next_index)] -
cos_look[
static_cast<std::size_t
>(theta_int)]);
143 [[nodiscard]]
inline float fast_sinf(
float theta_degrees) {
144 if (!std::isfinite(theta_degrees)) {
145 return std::numeric_limits<float>::quiet_NaN();
147 theta_degrees = std::fmod(theta_degrees, 360.0f);
148 if (theta_degrees < 0.0f) {
149 theta_degrees += 360.0f;
151 if (theta_degrees >= 360.0f) {
152 theta_degrees = 0.0f;
154 const int theta_int =
static_cast<int>(theta_degrees);
155 const int next_index = (theta_int + 1) % 360;
156 const float theta_frac = theta_degrees -
static_cast<float>(theta_int);
157 return sin_look[
static_cast<std::size_t
>(theta_int)] + theta_frac * (
sin_look[
static_cast<std::size_t
>(next_index)] -
sin_look[
static_cast<std::size_t
>(theta_int)]);
166 [[nodiscard]]
inline int rrand(
int x,
int y) {
170 return x + (std::rand() % (y - x + 1));
186 constexpr vec2D(
float x_value,
float y_value) :
x(x_value),
y(y_value) {}
189 void Set(
float x_value,
float y_value) {
201 FromEigen(ToEigen() + v.ToEigen(), *
this);
210 FromEigen(ToEigen() - v.ToEigen(), *
this);
215 [[nodiscard]]
vec2D operator*(
float k)
const {
return FromEigen(ToEigen() * k); }
218 [[nodiscard]]
vec2D Scale(
float k)
const {
return *
this * k; }
221 void ScaleThis(
float k) { FromEigen(ToEigen() * k, *
this); }
224 [[nodiscard]]
float DotProduct(
const vec2D &v)
const {
return ToEigen().dot(v.ToEigen()); }
227 [[nodiscard]]
float Length()
const {
return ToEigen().norm(); }
231 const float length =
Length();
233 FromEigen(Eigen::Vector2f::Zero(), *
this);
236 FromEigen(ToEigen().normalized(), *
this);
247 const float denom =
Length() * v.Length();
252 [[nodiscard]] std::string
Print(
const std::string &name =
"v")
const {
253 std::ostringstream out;
254 out << name <<
'<' <<
x <<
',' <<
y <<
'>';
259 [[nodiscard]] Eigen::Vector2f ToEigen()
const {
return {
x,
y}; }
261 [[nodiscard]]
static vec2D FromEigen(
const Eigen::Vector2f &v) {
return {v.x(), v.y()}; }
263 static void FromEigen(
const Eigen::Vector2f &v,
vec2D &out) { out.Set(v.x(), v.y()); }
267 inline std::ostream &
operator<<(std::ostream &out,
const vec2D &v) {
return out << v.Print(); }
270 inline std::istream &
operator>>(std::istream &in,
vec2D &v) {
return in >> v.x >> v.y; }
288 constexpr vec3D(
float x_value,
float y_value,
float z_value) :
x(x_value),
y(y_value),
z(z_value) {}
291 void Set(
float x_value,
float y_value,
float z_value) {
304 FromEigen(ToEigen() + v.ToEigen(), *
this);
313 FromEigen(ToEigen() - v.ToEigen(), *
this);
318 [[nodiscard]]
vec3D operator*(
float k)
const {
return FromEigen(ToEigen() * k); }
321 [[nodiscard]]
vec3D Scale(
float k)
const {
return *
this * k; }
324 void ScaleThis(
float k) { FromEigen(ToEigen() * k, *
this); }
327 [[nodiscard]]
float DotProduct(
const vec3D &v)
const {
return ToEigen().dot(v.ToEigen()); }
333 [[nodiscard]]
float Length()
const {
return ToEigen().norm(); }
337 const float len =
Length();
339 FromEigen(Eigen::Vector3f::Zero(), *
this);
342 FromEigen(ToEigen().normalized(), *
this);
353 const float denom =
Length() * v.Length();
358 [[nodiscard]] std::string
Print(
const std::string &name =
"v")
const {
359 std::ostringstream out;
360 out << name <<
'<' <<
x <<
',' <<
y <<
',' <<
z <<
'>';
365 [[nodiscard]] Eigen::Vector3f ToEigen()
const {
return {
x,
y,
z}; }
367 [[nodiscard]]
static vec3D FromEigen(
const Eigen::Vector3f &v) {
return {v.x(), v.y(), v.z()}; }
369 static void FromEigen(
const Eigen::Vector3f &v,
vec3D &out) { out.Set(v.x(), v.y(), v.z()); }
373 inline std::ostream &
operator<<(std::ostream &out,
const vec3D &v) {
return out << v.Print(); }
376 inline std::istream &
operator>>(std::istream &in,
vec3D &v) {
return in >> v.x >> v.y >> v.z; }
394 constexpr vec4D() :
x(0.0f),
y(0.0f),
z(0.0f),
w(1.0f) {}
397 constexpr vec4D(
float x_value,
float y_value,
float z_value,
float w_value = 1.0f) :
x(x_value),
y(y_value),
z(z_value),
w(w_value) {}
400 void Set(
float x_value,
float y_value,
float z_value,
float w_value = 1.0f) {
443 [[nodiscard]]
vec4D Scale(
float k)
const {
return *
this * k; }
471 const float len =
Length();
489 const float denom =
Length() * v.Length();
497 [[nodiscard]]
vec4D Build(
const vec4D &from,
const vec4D &to)
const {
return {to.x - from.x, to.y - from.y, to.z - from.z, 0.0f}; }
500 [[nodiscard]] std::string
Print(
const std::string &name =
"v")
const {
501 std::ostringstream out;
502 out << name <<
'<' <<
x <<
',' <<
y <<
',' <<
z <<
',' <<
w <<
'>';
508 inline std::ostream &
operator<<(std::ostream &out,
const vec4D &v) {
return out << v.Print(); }
511 inline std::istream &
operator>>(std::istream &in,
vec4D &v) {
return in >> v.x >> v.y >> v.z >> v.w; }
523 constexpr Mat1D(
float m0,
float m1) :
mat{m0, m1} {}
526 void Set(
float m0,
float m1) {
542 constexpr Mat1x3D(
float m0,
float m1,
float m2) :
mat{m0, m1, m2} {}
555 constexpr Mat1x4D(
float m0,
float m1,
float m2,
float m3) :
mat{m0, m1, m2, m3} {}
575 Mat2D(
float m00,
float m01,
float m10,
float m11) {
Set(m00, m01, m10, m11); }
578 void Set(
float m00,
float m01,
float m10,
float m11) { ToEigen() << m00, m01, m10, m11; }
593 [[nodiscard]]
float Determinate()
const {
return ToEigen().determinant(); }
605 out = FromEigen(ToEigen().inverse());
617 const float d = a.Determinate();
621 const Eigen::Vector2f rhs(b.mat[0], b.mat[1]);
622 const Eigen::Vector2f result = a.ToEigen().transpose().partialPivLu().solve(rhs);
623 out.Set(result.x(), result.y());
628 using EigenMatrix = Eigen::Matrix<float, 2, 2, Eigen::RowMajor>;
630 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() {
return Eigen::Map<EigenMatrix>(&
mat[0][0]); }
632 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen()
const {
return Eigen::Map<const EigenMatrix>(&
mat[0][0]); }
634 template <
typename Derived> [[nodiscard]]
static Mat2D FromEigen(
const Eigen::MatrixBase<Derived> &m) {
651 Mat3D(
float m00,
float m01,
float m02,
float m10,
float m11,
float m12,
float m20,
float m21,
float m22) {
Set(m00, m01, m02, m10, m11, m12, m20, m21, m22); }
654 void Set(
float m00,
float m01,
float m02,
float m10,
float m11,
float m12,
float m20,
float m21,
float m22) { ToEigen() << m00, m01, m02, m10, m11, m12, m20, m21, m22; }
664 const Eigen::Vector3f result = ToEigen().transpose() * Eigen::Vector3f(in.x, in.y, in.z);
665 return {result.x(), result.y(), result.z()};
672 [[nodiscard]]
float Determinate()
const {
return ToEigen().determinant(); }
684 out = FromEigen(ToEigen().inverse());
696 if (std::fabs(a.Determinate()) <=
EPSILON) {
699 const Eigen::Vector3f rhs(b.mat[0], b.mat[1], b.mat[2]);
700 const Eigen::Vector3f result = a.ToEigen().transpose().partialPivLu().solve(rhs);
701 Eigen::Map<Eigen::Vector3f>(out.mat) = result;
706 using EigenMatrix = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;
708 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() {
return Eigen::Map<EigenMatrix>(&
mat[0][0]); }
710 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen()
const {
return Eigen::Map<const EigenMatrix>(&
mat[0][0]); }
712 template <
typename Derived> [[nodiscard]]
static Mat3D FromEigen(
const Eigen::MatrixBase<Derived> &m) {
729 Mat4D(
float m00,
float m01,
float m02,
float m03,
float m10,
float m11,
float m12,
float m13,
float m20,
float m21,
float m22,
float m23,
float m30,
float m31,
float m32,
float m33) {
Set(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33); }
732 void Set(
float m00,
float m01,
float m02,
float m03,
float m10,
float m11,
float m12,
float m13,
float m20,
float m21,
float m22,
float m23,
float m30,
float m31,
float m32,
float m33) { ToEigen() << m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; }
751 const Eigen::Vector4f result = ToEigen().transpose() * Eigen::Vector4f(in.x, in.y, in.z, in.w);
752 return {result.x(), result.y(), result.z(), result.w()};
767 void MulVec(std::span<const vec4D> input, std::span<vec4D> output)
const {
768 if (input.size() != output.size()) {
769 throw std::invalid_argument(
"Mat4D::MulVec batch spans must have equal sizes");
775 static_assert(std::is_standard_layout_v<vec4D>);
776 static_assert(
sizeof(
vec4D) ==
sizeof(float) * 4);
777 using VertexMatrix = Eigen::Matrix<float, 4, Eigen::Dynamic, Eigen::ColMajor>;
778 const Eigen::Index vertex_count =
static_cast<Eigen::Index
>(input.size());
779 const Eigen::Map<const VertexMatrix, Eigen::Unaligned> input_matrix(&input.front().x, 4, vertex_count);
780 Eigen::Map<VertexMatrix, Eigen::Unaligned> output_matrix(&output.front().x, 4, vertex_count);
781 output_matrix.noalias() = ToEigen().transpose() * input_matrix;
787 return {r.x, r.y, r.z};
799 Eigen::FullPivLU<EigenMatrix> decomposition(ToEigen());
800 decomposition.setThreshold(
EPSILON);
801 if (!decomposition.isInvertible()) {
804 out = FromEigen(decomposition.inverse());
809 void BuildXYZ(
float theta_x,
float theta_y,
float theta_z) {
810 const float cx = std::cos(
deg2rad(theta_x));
811 const float sx = std::sin(
deg2rad(theta_x));
812 const float cy = std::cos(
deg2rad(theta_y));
813 const float sy = std::sin(
deg2rad(theta_y));
814 const float cz = std::cos(
deg2rad(theta_z));
815 const float sz = std::sin(
deg2rad(theta_z));
817 Mat4D mx(1, 0, 0, 0, 0, cx, sx, 0, 0, -sx, cx, 0, 0, 0, 0, 1);
818 Mat4D my(cy, 0, -sy, 0, 0, 1, 0, 0, sy, 0, cy, 0, 0, 0, 0, 1);
819 Mat4D mz(cz, sz, 0, 0, -sz, cz, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
820 *
this =
mx * my * mz;
824 using EigenMatrix = Eigen::Matrix<float, 4, 4, Eigen::RowMajor>;
826 [[nodiscard]] Eigen::Map<EigenMatrix> ToEigen() {
return Eigen::Map<EigenMatrix>(&
mat[0][0]); }
828 [[nodiscard]] Eigen::Map<const EigenMatrix> ToEigen()
const {
return Eigen::Map<const EigenMatrix>(&
mat[0][0]); }
830 template <
typename Derived> [[nodiscard]]
static Mat4D FromEigen(
const Eigen::MatrixBase<Derived> &m) {
885 Eigen::Matrix2f directions;
886 directions <<
v.x, line.v.x,
v.y, line.v.y;
887 const float det = directions.determinant();
888 if (std::fabs(det) <=
EPSILON) {
891 const Eigen::Vector2f delta(line.p0.x -
p0.x, line.p0.y -
p0.y);
892 const Eigen::Vector2f parameters = directions.partialPivLu().solve(delta);
893 t_this = parameters.x();
894 t_other = -parameters.y();
895 return (t_this >= 0.0f && t_this <= 1.0f && t_other >= 0.0f && t_other <= 1.0f) ? 1 : 2;
906 float t_other = 0.0f;
907 const int result =
Intersect(line, t_this, t_other);
1032 constexpr QuatType(
float x_value,
float y_value,
float z_value,
float w_value) :
x(x_value),
y(y_value),
z(z_value),
w(w_value) {}
1041 [[nodiscard]]
constexpr QuatType operator*(
const QuatType &q)
const {
return {
w * q.x +
x * q.w +
y * q.z -
z * q.y,
w * q.y -
x * q.z +
y * q.w +
z * q.x,
w * q.z +
x * q.y -
y * q.x +
z * q.w,
w * q.w -
x * q.x -
y * q.y -
z * q.z}; }
1068 [[nodiscard]]
float Norm()
const {
return std::sqrt(
Norm2()); }
1072 const float norm =
Norm();
1083 const float n2 =
Norm2();
1105 const float half =
deg2rad(theta_degrees) * 0.5f;
1106 const float s = std::sin(half);
1115 vec3D n(axis.x, axis.y, axis.z);
1120 void EulerZYX(
float theta_x,
float theta_y,
float theta_z) {
1121 const float hx =
deg2rad(theta_x) * 0.5f;
1122 const float hy =
deg2rad(theta_y) * 0.5f;
1123 const float hz =
deg2rad(theta_z) * 0.5f;
1124 const float cx = std::cos(hx);
1125 const float sx = std::sin(hx);
1126 const float cy = std::cos(hy);
1127 const float sy = std::sin(hy);
1128 const float cz = std::cos(hz);
1129 const float sz = std::sin(hz);
1130 w = cz * cy * cx + sz * sy * sx;
1131 x = cz * cy * sx - sz * sy * cx;
1132 y = cz * sy * cx + sz * cy * sx;
1133 z = sz * cy * cx - cz * sy * sx;
1140 const float s = std::sqrt(std::max(0.0f, 1.0f - q.w * q.w));
1142 axis.Set(1.0f, 0.0f, 0.0f);
1144 axis.Set(q.x / s, q.y / s, q.z / s);
1146 if (theta_degrees !=
nullptr) {
1147 *theta_degrees =
rad2deg(2.0f * std::acos(std::clamp(q.w, -1.0f, 1.0f)));
1201 std::vector<Triangle>
polys;
1217 for (
auto &poly :
polys) {
1218 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1221 for (
auto &vertex : poly.vlist) {
1222 vertex = mrot.MulVec(vertex);
1229 for (
auto &poly :
polys) {
1230 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1234 const vec4D v =
vec4D().Build(poly.tlist[0], poly.tlist[2]);
1235 const vec4D n = u.CrossProduct(v);
1237 if (n.DotProduct(view) <= 0.0f) {
1248 for (
auto &poly :
polys) {
1249 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1252 for (
int i = 0; i < 3; ++i) {
1253 poly.tlist[i] = poly.vlist[i] + pos;
1260 polys.push_back(triangle);
1302 std::vector<vec4D>
local;
1305 std::vector<vec4D>
trans;
1311 std::vector<Triangle>
vlist;
1335 std::cout <<
"mxvk_math_eigen: loading PLG model: " << path <<
'\n';
1336 const auto load_failed = [&path](
const char *reason) {
1337 std::cerr <<
"mxvk_math_eigen: failed to load PLG model '" << path <<
"': " << reason <<
'\n';
1341 std::ifstream file(path);
1342 if (!file.is_open()) {
1343 return load_failed(
"could not open file");
1346 const auto read_data_line = [&file](std::string &line) {
1347 while (std::getline(file, line)) {
1348 const std::size_t comment = line.find(
'#');
1349 if (comment != std::string::npos) {
1350 line.erase(comment);
1352 if (line.find_first_not_of(
" \t\r\n") != std::string::npos) {
1360 if (!read_data_line(line)) {
1361 return load_failed(
"missing model header");
1365 int vertex_count = 0;
1367 std::istringstream header(line);
1368 if (!(header >> name >> vertex_count >> poly_count) || vertex_count < 0 || poly_count < 0) {
1369 return load_failed(
"invalid model header");
1371 std::cout <<
"mxvk_math_eigen: PLG header parsed (object='" << name <<
"', vertices=" << vertex_count <<
", triangles=" << poly_count <<
")\n";
1373 std::vector<vec4D> loaded_local;
1374 std::vector<vec4D> loaded_trans;
1375 std::vector<vec2D> loaded_texcoords;
1376 std::vector<Triangle> loaded_vlist;
1377 loaded_local.reserve(
static_cast<std::size_t
>(vertex_count));
1378 loaded_trans.resize(
static_cast<std::size_t
>(vertex_count));
1379 loaded_texcoords.reserve(
static_cast<std::size_t
>(vertex_count));
1380 loaded_vlist.reserve(
static_cast<std::size_t
>(poly_count));
1382 for (
int i = 0; i < vertex_count; ++i) {
1383 if (!read_data_line(line)) {
1384 return load_failed(
"vertex data ended early");
1387 Eigen::Vector3f coordinates;
1388 std::istringstream vertex_line(line);
1389 if (!(vertex_line >> coordinates.x() >> coordinates.y() >> coordinates.z()) || !coordinates.allFinite()) {
1390 return load_failed(
"invalid vertex data");
1394 if (vertex_line >> texcoord.x) {
1395 if (!(vertex_line >> texcoord.y) || !std::isfinite(texcoord.x) || !std::isfinite(texcoord.y)) {
1396 return load_failed(
"invalid texture coordinates");
1400 const Eigen::Vector3f scaled = coordinates.cwiseProduct(Eigen::Vector3f(scale.x, scale.y, scale.z));
1401 if (!scaled.allFinite()) {
1402 return load_failed(
"scaled vertex is not finite");
1404 loaded_local.emplace_back(scaled.x(), scaled.y(), scaled.z(), 1.0f);
1405 loaded_texcoords.push_back(texcoord);
1408 for (
int i = 0; i < poly_count; ++i) {
1409 if (!read_data_line(line)) {
1410 return load_failed(
"triangle data ended early");
1415 std::istringstream polygon_line(line);
1416 if (!(polygon_line >> std::hex >> tri.state >> std::dec >> count) || count != 3 || !(polygon_line >> tri.vert[0] >> tri.vert[1] >> tri.vert[2])) {
1417 return load_failed(
"invalid triangle data");
1419 for (
const int index : tri.vert) {
1420 if (index < 0 || index >= vertex_count) {
1421 return load_failed(
"triangle vertex index is out of range");
1424 loaded_vlist.push_back(tri);
1427 for (
auto &triangle : loaded_vlist) {
1434 local = std::move(loaded_local);
1435 trans = std::move(loaded_trans);
1436 texcoords = std::move(loaded_texcoords);
1437 vlist = std::move(loaded_vlist);
1441 std::cout <<
"mxvk_math_eigen: PLG model ready (object='" <<
object_name <<
"', average radius=" <<
avg_rad <<
", maximum radius=" <<
max_rad <<
")\n";
1446 [[nodiscard]]
bool LoadMX(
const std::string &path,
const vec4D &scale,
const vec4D &obj_pos,
const vec4D &rotation) {
return LoadPLG(path, scale, obj_pos, rotation); }
1457 std::cout <<
"mxvk_math_eigen: loading OBJ model: " << path <<
'\n';
1461 std::cerr <<
"mxvk_math_eigen: failed to load OBJ model '" << path <<
"': " << error <<
'\n';
1465 std::unordered_map<std::string, int> material_indices;
1466 for (std::size_t index = 0; index < loaded.
materials.size(); ++index) {
1467 material_indices.emplace(loaded.
materials[index].name,
static_cast<int>(index));
1470 std::vector<vec4D> loaded_local;
1471 std::vector<vec2D> loaded_texcoords;
1472 std::vector<Triangle> loaded_vlist;
1473 loaded_local.reserve(loaded.
triangles.size() * 3);
1474 loaded_texcoords.reserve(loaded.
triangles.size() * 3);
1475 loaded_vlist.reserve(loaded.
triangles.size());
1481 triangle.source_object_name = source_triangle.
object_name;
1482 const auto material = material_indices.find(source_triangle.
material_name);
1483 if (material != material_indices.end()) {
1484 triangle.material_index = material->second;
1485 triangle.color = material_color(loaded.
materials[
static_cast<std::size_t
>(material->second)]);
1488 for (std::size_t vertex_index = 0; vertex_index < source_triangle.
vertices.size(); ++vertex_index) {
1490 const Eigen::Vector3f scaled = Eigen::Vector3f(source_vertex.
position[0], source_vertex.
position[1], source_vertex.
position[2]).cwiseProduct(Eigen::Vector3f(scale.x, scale.y, scale.z));
1491 if (!scaled.allFinite()) {
1492 std::cerr <<
"mxvk_math_eigen: failed to load OBJ model '" << path <<
"': scaled vertex is not finite\n";
1495 triangle.vert[vertex_index] =
static_cast<int>(loaded_local.size());
1496 loaded_local.emplace_back(scaled.x(), scaled.y(), scaled.z(), 1.0f);
1497 loaded_texcoords.emplace_back(source_vertex.
texcoord[0], source_vertex.
texcoord[1]);
1499 loaded_vlist.push_back(triangle);
1504 num_polys =
static_cast<int>(loaded_vlist.size());
1505 local = std::move(loaded_local);
1507 texcoords = std::move(loaded_texcoords);
1508 vlist = std::move(loaded_vlist);
1522 [[nodiscard]]
bool LoadMTL(
const std::string &path) {
1523 std::vector<OBJMaterial> loaded_materials;
1526 std::cerr <<
"mxvk_math_eigen: failed to load MTL file '" << path <<
"': " << error <<
'\n';
1530 std::unordered_map<std::string, int> material_indices;
1531 for (std::size_t index = 0; index < loaded_materials.size(); ++index) {
1532 material_indices.emplace(loaded_materials[index].name,
static_cast<int>(index));
1534 for (std::size_t index = 0; index <
vlist.size(); ++index) {
1535 const auto material = material_indices.find(
vlist[index].material_name);
1536 vlist[index].material_index = material == material_indices.end() ? -1 : material->second;
1537 if (material != material_indices.end()) {
1538 vlist[index].color = material_color(loaded_materials[
static_cast<std::size_t
>(material->second)]);
1542 materials = std::move(loaded_materials);
1551 for (std::size_t i = 0; i <
local.size(); ++i) {
1554 }
else if (type == 1) {
1555 for (
auto &vertex :
trans) {
1563 auto transform = [&mrot](std::vector<vec4D> &vertices) {
1564 for (
auto &vertex : vertices) {
1565 vertex = mrot.MulVec(vertex);
1570 }
else if (type == 1) {
1572 }
else if (type == 2) {
1574 for (std::size_t i = 0; i <
local.size(); ++i) {
1582 for (
auto &poly :
vlist) {
1586 if (poly.vert[0] < 0 || poly.vert[1] < 0 || poly.vert[2] < 0 ||
static_cast<std::size_t
>(poly.vert[0]) >=
trans.size() ||
static_cast<std::size_t
>(poly.vert[1]) >=
trans.size() ||
static_cast<std::size_t
>(poly.vert[2]) >=
trans.size()) {
1589 const vec4D u =
vec4D().
Build(
trans[
static_cast<std::size_t
>(poly.vert[0])],
trans[
static_cast<std::size_t
>(poly.vert[1])]);
1590 const vec4D v =
vec4D().Build(
trans[
static_cast<std::size_t
>(poly.vert[0])],
trans[
static_cast<std::size_t
>(poly.vert[2])]);
1591 const vec4D n = u.CrossProduct(v);
1593 if (n.DotProduct(view) <= 0.0f) {
1601 for (
const auto &poly :
vlist) {
1603 for (
int i = 0; i < 3; ++i) {
1604 const auto index =
static_cast<std::size_t
>(poly.vert[i]);
1605 if (index <
local.size()) {
1606 tri.vlist[i] =
local[index];
1608 if (index <
trans.size()) {
1609 tri.tlist[i] =
trans[index];
1612 list.BuildRenderList(tri);
1618 for (
auto &poly :
vlist) {
1628 if (
local.empty()) {
1631 for (
const auto &vertex :
local) {
1632 const float dist = Eigen::Vector3f(vertex.x, vertex.y, vertex.z).norm();
1645 const auto channel = [](
float value) {
return static_cast<MXCOLOR>(std::lround(std::clamp(value, 0.0f, 1.0f) * 255.0f)); };
1646 return (channel(material.
dissolve) << 24u) | (channel(material.
diffuse[0]) << 16u) | (channel(material.
diffuse[1]) << 8u) | channel(material.
diffuse[2]);
1733 mcam.LoadIdentity();
1734 mper.LoadIdentity();
1735 mscr.LoadIdentity();
1740 pos.Set(100.0f, 200.0f, 300.0f);
1741 dir.Set(-48.0f, 0.0f, 0.0f);
1746 void Init(
int camera_attr,
const vec4D &camera_pos,
const vec4D &camera_dir,
const vec4D *camera_target,
float near_z,
float far_z,
float fov_degrees,
float width,
float height) {
1750 target = camera_target !=
nullptr ? *camera_target :
vec4D();
1762 mcam.LoadIdentity();
1763 mper.LoadIdentity();
1764 mscr.LoadIdentity();
1769 Mat4D translation(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -
pos.x, -
pos.y, -
pos.z, 1);
1771 rotation.BuildXYZ(-
dir.x, -
dir.y, -
dir.z);
1772 mcam = translation * rotation;
1779 v.Set(0.0f, 1.0f, 0.0f);
1780 u =
v.CrossProduct(
n);
1782 v =
n.CrossProduct(
u);
1784 Mat4D translation(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -
pos.x, -
pos.y, -
pos.z, 1);
1785 Mat4D uvn(
u.x,
v.x,
n.x, 0,
u.y,
v.y,
n.y, 0,
u.z,
v.z,
n.z, 0, 0, 0, 0, 1);
1786 mcam = translation * uvn;
1791 for (
auto &poly : list.polys) {
1792 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1795 for (
auto &vertex : poly.tlist) {
1796 vertex =
mcam.MulVec(vertex);
1803 for (
auto &vertex :
object.trans) {
1804 vertex =
mcam.MulVec(vertex);
1810 for (
auto &poly : list.polys) {
1811 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1814 for (
auto &vertex : poly.tlist) {
1815 if (std::fabs(vertex.z) <=
EPSILON) {
1818 const Eigen::Vector2f projected = Eigen::Vector2f(vertex.x, vertex.y).cwiseProduct(Eigen::Vector2f(
view_dist,
view_dist *
aspect_ratio)) / vertex.z;
1819 vertex.x = projected.x();
1820 vertex.y = projected.y();
1827 for (
auto &vertex :
object.trans) {
1828 if (std::fabs(vertex.z) <=
EPSILON) {
1831 const Eigen::Vector2f projected = Eigen::Vector2f(vertex.x, vertex.y).cwiseProduct(Eigen::Vector2f(
view_dist,
view_dist *
aspect_ratio)) / vertex.z;
1832 vertex.x = projected.x();
1833 vertex.y = projected.y();
1841 for (
auto &poly : list.polys) {
1842 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
1845 for (
auto &vertex : poly.tlist) {
1846 const Eigen::Vector2f screen = Eigen::Vector2f(alpha, beta) + Eigen::Vector2f(alpha, -beta).cwiseProduct(Eigen::Vector2f(vertex.x, vertex.y));
1847 vertex.x = screen.x();
1848 vertex.y = screen.y();
1857 for (
auto &vertex :
object.trans) {
1858 const Eigen::Vector2f screen = Eigen::Vector2f(alpha, beta) + Eigen::Vector2f(alpha, -beta).cwiseProduct(Eigen::Vector2f(vertex.x, vertex.y));
1859 vertex.x = screen.x();
1860 vertex.y = screen.y();
1866 struct SDLRendererPixelPlotter {
1876 SDL_RenderPoint(
renderer,
static_cast<float>(x),
static_cast<float>(y));
1881 struct VKSpritePixelPlotter {
1883 VK_Sprite *
sprite =
nullptr;
1891 sprite->drawSpriteRect(x, y, std::max(1,
size), std::max(1,
size));
1906 template <
typename PlotPixel>
void draw_line(
int x0,
int y0,
int x1,
int y1,
MXCOLOR color, PlotPixel &&plot_pixel) {
1907 const int dx = std::abs(x1 - x0);
1908 const int sx = x0 < x1 ? 1 : -1;
1909 const int dy = -std::abs(y1 - y0);
1910 const int sy = y0 < y1 ? 1 : -1;
1914 plot_pixel(x0, y0, color);
1915 if (x0 == x1 && y0 == y1) {
1918 const int e2 = 2 * err;
1931 inline void draw_line(SDL_Renderer *renderer,
int x0,
int y0,
int x1,
int y1,
MXCOLOR color) {
draw_line(x0, y0, x1, y1, color,
SDLRendererPixelPlotter{renderer}); }
1934 inline void draw_line(
VK_Sprite &sprite,
int x0,
int y0,
int x1,
int y1,
MXCOLOR color,
int pixel_size = 1) {
draw_line(x0, y0, x1, y1, color,
VKSpritePixelPlotter{&sprite, pixel_size}); }
1938 Eigen::Matrix2f edges;
1939 edges << p.x - a.x, b.x - a.x, p.y - a.y, b.y - a.y;
1940 return edges.determinant();
1954 if (std::fabs(area) <=
EPSILON) {
1958 const int min_x =
static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x})));
1959 const int max_x =
static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x})));
1960 const int min_y =
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y})));
1961 const int max_y =
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y})));
1963 for (
int y = min_y; y <= max_y; ++y) {
1964 for (
int x = min_x; x <= max_x; ++x) {
1965 const vec2D p(
static_cast<float>(x) + 0.5f,
static_cast<float>(y) + 0.5f);
1969 if ((area > 0.0f && w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) || (area < 0.0f && w0 <= 0.0f && w1 <= 0.0f && w2 <= 0.0f)) {
1970 plot_pixel(x, y, color);
1980 inline void draw_filled_triangle(
VK_Sprite &sprite,
const vec2D &p0,
const vec2D &p1,
const vec2D &p2,
MXCOLOR color,
int pixel_size = 1) {
draw_filled_triangle(p0, p1, p2, color,
VKSpritePixelPlotter{&sprite, pixel_size}); }
2004 [[nodiscard]]
float x_at(
int y)
const {
return x0 +
static_cast<float>(y - y0) * slope; }
2007 const int min_y = std::max(clip_min_y,
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y}))));
2008 const int max_y = std::min(clip_max_y,
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y}))));
2009 if (min_y > max_y) {
2013 std::array<SpanEdge, 3> edges{};
2015 const auto add_edge = [&](
vec2D a,
vec2D b) {
2016 if (std::fabs(a.y - b.y) <=
EPSILON) {
2023 const int y0 = std::max(min_y,
static_cast<int>(std::ceil(a.y - 0.5f)));
2024 const int y1 = std::min(max_y,
static_cast<int>(std::ceil(b.y - 0.5f)) - 1);
2029 SpanEdge &edge = edges[
static_cast<std::size_t
>(edge_count++)];
2032 edge.slope = (b.x - a.x) / (b.y - a.y);
2033 edge.x0 = a.x + ((
static_cast<float>(y0) + 0.5f) - a.y) * edge.slope;
2040 for (
int y = min_y; y <= max_y; ++y) {
2041 std::array<float, 3> intersections{};
2042 int intersection_count = 0;
2044 for (
int edge_index = 0; edge_index < edge_count; ++edge_index) {
2045 const SpanEdge &edge = edges[
static_cast<std::size_t
>(edge_index)];
2046 if (y >= edge.y0 && y <= edge.y1) {
2047 intersections[
static_cast<std::size_t
>(intersection_count++)] = edge.x_at(y);
2051 if (intersection_count < 2) {
2055 float min_x = intersections[0];
2056 float max_x = intersections[0];
2057 for (
int i = 1; i < intersection_count; ++i) {
2058 min_x = std::min(min_x, intersections[
static_cast<std::size_t
>(i)]);
2059 max_x = std::max(max_x, intersections[
static_cast<std::size_t
>(i)]);
2062 const int x0 =
static_cast<int>(std::ceil(min_x - 0.5f));
2063 const int x1 =
static_cast<int>(std::floor(max_x - 0.5f));
2065 draw_span(x0, x1, y, color);
2070 template <
typename DrawSpan>
void draw_filled_triangle_spans(
vec2D p0,
vec2D p1,
vec2D p2,
MXCOLOR color, DrawSpan &&draw_span) {
draw_filled_triangle_spans_clipped(p0, p1, p2, std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), color, std::forward<DrawSpan>(draw_span)); }
2118 void Begin(
int width,
int height, std::function<
void(
int,
int,
MXCOLOR)> plotter) {
2119 plot_pixel = std::move(plotter);
2120 plot_span = [
this](
int x0,
int x1,
int y,
MXCOLOR color) {
2121 for (
int x = x0; x <= x1; ++x) {
2122 plot_pixel(x, y, color);
2129 [[maybe_unused]]
static const bool PIPELINE_LOGGED = [width, height] {
2130 std::cout <<
"mxvk_math_eigen: software raster pipeline ready (" << width <<
'x' << height <<
")\n";
2136 void Begin(SDL_Renderer *renderer,
int width,
int height) {
2138 plot_span = [renderer](
int x0,
int x1,
int y,
MXCOLOR color) {
2139 if (renderer ==
nullptr) {
2143 SDL_RenderLine(renderer,
static_cast<float>(x0),
static_cast<float>(y),
static_cast<float>(x1),
static_cast<float>(y));
2150 plot_span = [&sprite, pixel_size](
int x0,
int x1,
int y,
MXCOLOR) {
2151 const int size = std::max(1, pixel_size);
2180 bool ClipLine(
int &x0,
int &y0,
int &x1,
int &y1)
const {
2183 const auto interpolate = [](
int dependent0,
int dependent1,
int independent0,
int independent1,
int boundary) {
2184 const double value =
static_cast<double>(dependent0) + (
static_cast<double>(dependent1) -
static_cast<double>(dependent0)) * (
static_cast<double>(boundary) -
static_cast<double>(independent0)) / (
static_cast<double>(independent1) -
static_cast<double>(independent0));
2185 return static_cast<int>(std::lround(value));
2189 if ((code0 | code1) == 0) {
2192 if ((code0 & code1) != 0) {
2196 const int out_code = code0 != 0 ? code0 : code1;
2200 if ((out_code &
CODE_N) != 0) {
2206 }
else if ((out_code &
CODE_S) != 0) {
2212 }
else if ((out_code &
CODE_E) != 0) {
2226 if (out_code == code0) {
2240 if (plot_pixel &&
ClipLine(x0, y0, x1, y1)) {
2241 draw_line(x0, y0, x1, y1, color, plot_pixel);
2256 if (!plot_pixel || first.z <=
EPSILON || second.z <=
EPSILON || !std::isfinite(first.x) || !std::isfinite(first.y) || !std::isfinite(first.z) || !std::isfinite(second.x) || !std::isfinite(second.y) || !std::isfinite(second.z)) {
2260 const int framebuffer_width =
max_clip_x + 1;
2261 const int framebuffer_height =
max_clip_y + 1;
2262 const std::size_t required_depth_values =
static_cast<std::size_t
>(framebuffer_width) *
static_cast<std::size_t
>(framebuffer_height);
2263 if (framebuffer_width <= 0 || framebuffer_height <= 0 || depth_buffer.size() < required_depth_values) {
2267 const float delta_x = second.x - first.x;
2268 const float delta_y = second.y - first.y;
2269 float first_fraction = 0.0f;
2270 float last_fraction = 1.0f;
2271 const auto clip_fraction = [&first_fraction, &last_fraction](
float direction,
float distance) {
2272 if (std::abs(direction) <=
EPSILON) {
2273 return distance >= 0.0f;
2276 const float fraction = distance / direction;
2277 if (direction < 0.0f) {
2278 first_fraction = std::max(first_fraction, fraction);
2280 last_fraction = std::min(last_fraction, fraction);
2282 return first_fraction <= last_fraction;
2285 if (!clip_fraction(-delta_x, first.x -
static_cast<float>(
clip_min_x)) || !clip_fraction(delta_x,
static_cast<float>(
clip_max_x) - first.x) || !clip_fraction(-delta_y, first.y -
static_cast<float>(
clip_min_y)) || !clip_fraction(delta_y,
static_cast<float>(
clip_max_y) - first.y)) {
2289 const float clipped_delta_x = delta_x * (last_fraction - first_fraction);
2290 const float clipped_delta_y = delta_y * (last_fraction - first_fraction);
2291 const float step_count = std::ceil(std::max(std::abs(clipped_delta_x), std::abs(clipped_delta_y)));
2292 if (step_count >
static_cast<float>(std::numeric_limits<int>::max())) {
2295 const int steps = std::max(1,
static_cast<int>(step_count));
2296 const float first_reciprocal_depth = 1.0f / first.z;
2297 const float second_reciprocal_depth = 1.0f / second.z;
2299 for (
int step = 0; step <= steps; ++step) {
2300 const float clipped_fraction =
static_cast<float>(step) /
static_cast<float>(steps);
2301 const float fraction = first_fraction + (last_fraction - first_fraction) * clipped_fraction;
2302 const int x =
static_cast<int>(std::lround(first.x + delta_x * fraction));
2303 const int y =
static_cast<int>(std::lround(first.y + delta_y * fraction));
2308 const float reciprocal_depth = first_reciprocal_depth + (second_reciprocal_depth - first_reciprocal_depth) * fraction;
2309 if (reciprocal_depth <=
EPSILON) {
2313 const float depth = 1.0f / reciprocal_depth;
2314 const std::size_t pixel_index =
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(framebuffer_width) +
static_cast<std::size_t
>(x);
2315 if (depth >= depth_buffer[pixel_index]) {
2319 depth_buffer[pixel_index] = depth;
2320 plot_pixel(x, y, color);
2326 DrawClippedLine(
static_cast<int>(std::lround(first.x)),
static_cast<int>(std::lround(first.y)),
static_cast<int>(std::lround(second.x)),
static_cast<int>(std::lround(second.y)), color);
2327 DrawClippedLine(
static_cast<int>(std::lround(second.x)),
static_cast<int>(std::lround(second.y)),
static_cast<int>(std::lround(third.x)),
static_cast<int>(std::lround(third.y)), color);
2328 DrawClippedLine(
static_cast<int>(std::lround(third.x)),
static_cast<int>(std::lround(third.y)),
static_cast<int>(std::lround(first.x)),
static_cast<int>(std::lround(first.y)), color);
2344 const int min_x =
static_cast<int>(std::floor(std::min({p0.x, p1.x, p2.x})));
2345 const int max_x =
static_cast<int>(std::ceil(std::max({p0.x, p1.x, p2.x})));
2346 const int min_y =
static_cast<int>(std::floor(std::min({p0.y, p1.y, p2.y})));
2347 const int max_y =
static_cast<int>(std::ceil(std::max({p0.y, p1.y, p2.y})));
2359 plot_span(x0, x1, y, pixel_color);
2369 for (
const auto &poly : list.polys) {
2370 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2379 for (
const auto &poly : list.polys) {
2380 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2389 for (
const auto &poly : list.polys) {
2390 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2402 for (
const auto &poly :
object.vlist) {
2403 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2406 const auto a =
static_cast<std::size_t
>(poly.vert[0]);
2407 const auto b =
static_cast<std::size_t
>(poly.vert[1]);
2408 const auto c =
static_cast<std::size_t
>(poly.vert[2]);
2409 if (a >=
object.trans.size() || b >=
object.trans.size() || c >=
object.trans.size()) {
2421 for (
const auto &poly :
object.vlist) {
2422 if (poly.state == 0 || (poly.state &
MX_BACKFACE) != 0) {
2425 const auto a =
static_cast<std::size_t
>(poly.vert[0]);
2426 const auto b =
static_cast<std::size_t
>(poly.vert[1]);
2427 const auto c =
static_cast<std::size_t
>(poly.vert[2]);
2428 if (a >=
object.trans.size() || b >=
object.trans.size() || c >=
object.trans.size()) {
2437 plot_pixel =
nullptr;
2438 plot_span =
nullptr;
2442 std::function<void(
int,
int,
MXCOLOR)> plot_pixel;
2443 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.