308 Math3DModelLoaderWindow(
const std::string &filename,
const std::string &texture_filename,
const std::string &asset_path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool enable_vsync,
bool repeat_texture,
bool disable_warp_fix,
bool disable_mipmap,
float mip_bias,
const FramebufferDimensions &framebuffer,
bool benchmark,
bool wireframe)
310 override_texture(wireframe ? Texture{} : load_texture(texture_filename, asset_path, !disable_mipmap)),
311 frame_width(framebuffer.width),
312 frame_height(framebuffer.height),
313 fallback_width(width),
314 fallback_height(height),
315 warp_fix_enabled(!disable_warp_fix),
316 mipmapping_enabled(!disable_mipmap),
317 mip_level_bias(mip_bias),
318 texture_repeat_enabled(repeat_texture),
319 benchmark_enabled(benchmark),
320 wireframe_enabled(wireframe) {
324 const std::filesystem::path path = model_path(filename, asset_path);
325#if defined(MXVK_OBJ_LOADER)
328 const mxvk::vec4D scale(MODEL_SCALE, MODEL_SCALE, MODEL_SCALE);
330#if defined(MXVK_OBJ_LOADER)
331 const bool model_loaded = model.LoadOBJ(path.string(), scale,
mxvk::vec4D(0.0f, 0.0f, 4.5f),
mxvk::vec4D());
333 const bool model_loaded = model.LoadPLG(path.string(), scale,
mxvk::vec4D(0.0f, 0.0f, 4.5f),
mxvk::vec4D());
336 throw mxvk::Exception(std::format(
"{}: failed to load {} model '{}'", APP_NAME, MODEL_FORMAT, path.string()));
338#if defined(MXVK_OBJ_LOADER)
339 filter_auxiliary_objects();
341 if (!wireframe_enabled) {
342 load_material_textures(asset_path, !disable_mipmap);
345#if defined(MXVK_USE_EIGEN_MATH)
346 local_vertex_batch.resize(4,
static_cast<Eigen::Index
>(model.local.size()));
347 camera_vertex_batch.resize(4,
static_cast<Eigen::Index
>(model.local.size()));
348 projected_vertex_batch.resize(4,
static_cast<Eigen::Index
>(model.local.size()));
349 inverse_vertex_depth.resize(
static_cast<Eigen::Index
>(model.local.size()));
350#if !defined(MXVK_OBJ_LOADER)
351 local_face_center_batch.resize(4,
static_cast<Eigen::Index
>(model.vlist.size()));
352 camera_face_center_batch.resize(4,
static_cast<Eigen::Index
>(model.vlist.size()));
354 local_face_normal_batch.resize(4,
static_cast<Eigen::Index
>(model.vlist.size()));
355 camera_face_normal_batch.resize(4,
static_cast<Eigen::Index
>(model.vlist.size()));
356 triangle_intensity.resize(
static_cast<Eigen::Index
>(model.vlist.size()));
357#if !defined(MXVK_OBJ_LOADER)
358 triangle_visible.resize(
static_cast<Eigen::Index
>(model.vlist.size()));
360 for (std::size_t index = 0; index < model.local.size(); ++index) {
361 local_vertex_batch.col(
static_cast<Eigen::Index
>(index)) =
362 Eigen::Vector4f(model.local[index].x, model.local[index].y, model.local[index].z, model.local[index].w);
365 camera_vertices.resize(model.local.size());
366 projected_vertices.resize(model.local.size());
368 initialize_face_geometry();
369 visible_faces.reserve(model.vlist.size());
370 std::cout << std::format(
"{}: loaded '{}' ({} vertices, {} triangles, {} materials)\n", APP_NAME, path.string(), model.num_vertices, model.num_polys, model.materials.size());
374 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
377 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE && !e.key.repeat) {
378 automatic_rotation = !automatic_rotation;
381 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
382 mouse_dragging =
true;
383 last_mouse_x = e.button.x;
384 last_mouse_y = e.button.y;
387 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
388 mouse_dragging =
false;
391 if (e.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
392 mouse_dragging =
false;
395 if (e.type == SDL_EVENT_MOUSE_MOTION && mouse_dragging) {
396 const float delta_x = e.motion.x - last_mouse_x;
397 const float delta_y = e.motion.y - last_mouse_y;
398 yaw_degrees = std::fmod(yaw_degrees + delta_x * MOUSE_ROTATION_SENSITIVITY, 360.0f);
399 pitch_degrees = std::clamp(
400 pitch_degrees + delta_y * MOUSE_ROTATION_SENSITIVITY,
403 last_mouse_x = e.motion.x;
404 last_mouse_y = e.motion.y;
407 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
408 const float delta = e.wheel.y != 0.0f ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
409 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
417 ensure_framebuffer();
418 if (frame_sprite ==
nullptr || frame_surface ==
nullptr || frame_format ==
nullptr) {
422 clear_frame(BACKGROUND_COLOR);
423 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
425 const std::uint64_t current_ticks = SDL_GetTicks();
426 const float elapsed_seconds = previous_frame_ticks == 0
428 :
static_cast<float>(current_ticks - previous_frame_ticks) * 0.001f;
429 previous_frame_ticks = current_ticks;
430 if (automatic_rotation && !mouse_dragging) {
431 yaw_degrees = std::fmod(yaw_degrees + elapsed_seconds * 42.0f, 360.0f);
434 if (benchmark_enabled && benchmark_frame_count == 0) {
435 benchmark_stopwatch =
436 std::make_unique<StopWatch<HighResolutionClockPolicy>>(benchmark_name);
440 rotation.
BuildXYZ(pitch_degrees, yaw_degrees, 0.0f);
441 transform_and_project_vertices(rotation);
443 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
445 visible_faces.clear();
446 build_visible_faces(rotation, light_direction);
448 for (
const FaceDraw &face : visible_faces) {
449 if (wireframe_enabled) {
450 wireframe_pipeline.DrawWireframeTriangle(
457 draw_gradient_triangle(face);
461 if (benchmark_stopwatch !=
nullptr) {
462 ++benchmark_frame_count;
463 if (benchmark_frame_count == BENCHMARK_FRAME_COUNT) {
464 benchmark_stopwatch->Stop();
465 benchmark_stopwatch.reset();
470 frame_sprite->updateTexture(frame_surface->pixels, frame_width, frame_height, frame_surface->pitch);
471 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
477 Texture override_texture;
478 std::vector<Texture> material_textures;
479 SurfacePtr frame_surface;
480#if defined(MXVK_USE_EIGEN_MATH)
481 using VertexBatch = Eigen::Matrix<float, 4, Eigen::Dynamic, Eigen::RowMajor>;
482 VertexBatch local_vertex_batch;
483 VertexBatch camera_vertex_batch;
484 VertexBatch projected_vertex_batch;
485 Eigen::RowVectorXf inverse_vertex_depth;
486 VertexBatch local_face_center_batch;
487 VertexBatch camera_face_center_batch;
488 VertexBatch local_face_normal_batch;
489 VertexBatch camera_face_normal_batch;
490 Eigen::RowVectorXf triangle_intensity;
491 Eigen::Array<bool, 1, Eigen::Dynamic> triangle_visible;
493 std::vector<mxvk::vec4D> camera_vertices;
494 std::vector<mxvk::vec4D> projected_vertices;
495 std::vector<mxvk::vec4D> local_face_centers;
496 std::vector<mxvk::vec4D> camera_face_centers;
497 std::vector<mxvk::vec4D> local_face_normals;
498 std::vector<mxvk::vec4D> camera_face_normals;
500 std::vector<FaceDraw> visible_faces;
501 std::vector<float> depth_buffer;
502 const SDL_PixelFormatDetails *frame_format =
nullptr;
504 int frame_width = 1280;
505 int frame_height = 720;
506 int fallback_width = 1280;
507 int fallback_height = 720;
508 float camera_distance = 4.25f;
509 float pitch_degrees = 0.0f;
510 float yaw_degrees = 0.0f;
511 float last_mouse_x = 0.0f;
512 float last_mouse_y = 0.0f;
513 bool mouse_dragging =
false;
514 bool automatic_rotation =
true;
515 std::uint64_t previous_frame_ticks = 0;
516 bool warp_fix_enabled =
true;
517 bool mipmapping_enabled =
true;
518 float mip_level_bias = 0.0f;
519 bool texture_repeat_enabled =
false;
520 bool benchmark_enabled =
false;
521 bool wireframe_enabled =
false;
522 std::size_t benchmark_frame_count = 0;
523 std::string benchmark_name =
524 std::format(
"{} geometry draw ({} backend, {} frames)", MODEL_FORMAT, BACKEND_NAME, BENCHMARK_FRAME_COUNT);
525 std::unique_ptr<StopWatch<HighResolutionClockPolicy>> benchmark_stopwatch;
527 static constexpr std::size_t BENCHMARK_FRAME_COUNT = 60 * 10;
529#if defined(MXVK_OBJ_LOADER)
530 void fit_model_to_view() {
531 if (model.
vlist.empty()) {
532 throw mxvk::Exception(std::format(
"{}: cannot fit an OBJ model with no triangles", APP_NAME));
536 std::numeric_limits<float>::max(),
537 std::numeric_limits<float>::max(),
538 std::numeric_limits<float>::max());
540 std::numeric_limits<float>::lowest(),
541 std::numeric_limits<float>::lowest(),
542 std::numeric_limits<float>::lowest());
544 for (
const int vertex_index : triangle.
vert) {
545 const mxvk::vec4D &vertex = model.
local[
static_cast<std::size_t
>(vertex_index)];
546 minimum.x = std::min(minimum.x, vertex.
x);
547 minimum.y = std::min(minimum.y, vertex.
y);
548 minimum.z = std::min(minimum.z, vertex.
z);
549 maximum.x = std::max(maximum.x, vertex.
x);
550 maximum.y = std::max(maximum.y, vertex.
y);
551 maximum.z = std::max(maximum.z, vertex.
z);
555 const mxvk::vec4D center(
556 (minimum.x + maximum.x) * 0.5f,
557 (minimum.y + maximum.y) * 0.5f,
558 (minimum.z + maximum.z) * 0.5f);
559 float source_radius = 0.0f;
560 for (
const mxvk::Triangle &triangle : model.vlist) {
561 for (
const int vertex_index : triangle.
vert) {
562 const mxvk::vec4D &vertex = model.local[
static_cast<std::size_t
>(vertex_index)];
563 source_radius = std::max(
566 (vertex.
x - center.x) * (vertex.
x - center.x) +
567 (vertex.
y - center.y) * (vertex.
y - center.y) +
568 (vertex.
z - center.z) * (vertex.
z - center.z)));
571 if (!std::isfinite(source_radius) || source_radius <=
mxvk::EPSILON) {
572 throw mxvk::Exception(std::format(
"{}: cannot fit OBJ model with degenerate bounds", APP_NAME));
575 const float fit_scale = MODEL_FIT_RADIUS / source_radius;
576 for (mxvk::vec4D &vertex : model.local) {
577 vertex.
x = (vertex.
x - center.x) * fit_scale;
578 vertex.
y = (vertex.
y - center.y) * fit_scale;
579 vertex.
z = (vertex.
z - center.z) * fit_scale;
582 std::cout << std::format(
583 "{}: centered model and scaled radius {:.3f} to {:.3f} (scale {:.6f})\n",
591 void filter_auxiliary_objects() {
592 std::unordered_map<std::string, std::size_t> triangle_counts;
593 for (
const mxvk::Triangle &triangle : model.vlist) {
596 if (triangle_counts.size() <= 1) {
600 const auto dominant = std::max_element(
601 triangle_counts.begin(),
602 triangle_counts.end(),
603 [](
const auto &left,
const auto &right) {
604 return left.second < right.second;
606 constexpr float DOMINANT_OBJECT_FRACTION = 0.95f;
607 if (
static_cast<float>(dominant->second) <
608 static_cast<float>(model.vlist.size()) * DOMINANT_OBJECT_FRACTION) {
612 const std::size_t original_triangle_count = model.vlist.size();
613 std::erase_if(model.vlist, [&dominant](
const mxvk::Triangle &triangle) {
614 return triangle.source_object_name != dominant->first;
616 model.num_polys =
static_cast<int>(model.vlist.size());
617 model.object_name = dominant->first;
618 std::cout << std::format(
619 "{}: selected dominant OBJ object '{}' ({} triangles); ignored {} auxiliary triangle(s)\n",
623 original_triangle_count - model.vlist.size());
626 void load_material_textures(
const std::string &asset_path,
bool generate_mipmaps) {
627 if (!override_texture.empty()) {
631 material_textures.resize(model.materials.size());
632 for (std::size_t index = 0; index < model.materials.size(); ++index) {
633 const std::string &
texture_path = model.materials[index].diffuse_map;
635 material_textures[index] =
load_texture(texture_path, asset_path, generate_mipmaps);
640 [[nodiscard]]
const Texture *face_texture(
int material_index)
const {
641 if (!override_texture.empty()) {
642 return &override_texture;
644 if (material_index >= 0 &&
645 static_cast<std::size_t
>(material_index) < material_textures.size() &&
646 !material_textures[
static_cast<std::size_t
>(material_index)].empty()) {
647 return &material_textures[
static_cast<std::size_t
>(material_index)];
652#if defined(MXVK_USE_EIGEN_MATH)
653 static void transform_eigen_batch(
const mxvk::Mat4D &matrix,
const VertexBatch &input, VertexBatch &output) {
654 for (
int component = 0; component < 4; ++component) {
655 output.row(component).array() =
656 input.row(0).array() * matrix.
mat[0][component] +
657 input.row(1).array() * matrix.
mat[1][component] +
658 input.row(2).array() * matrix.
mat[2][component] +
659 input.row(3).array() * matrix.
mat[3][component];
664 void initialize_face_geometry() {
665#if !defined(MXVK_USE_EIGEN_MATH)
666 local_face_centers.resize(model.vlist.size());
667 camera_face_centers.resize(model.vlist.size());
668 local_face_normals.resize(model.vlist.size());
669 camera_face_normals.resize(model.vlist.size());
671 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
672 const mxvk::Triangle &triangle = model.
vlist[index];
673 const mxvk::vec4D &a = model.local[
static_cast<std::size_t
>(triangle.
vert[0])];
674 const mxvk::vec4D &b = model.local[
static_cast<std::size_t
>(triangle.
vert[1])];
675 const mxvk::vec4D &c = model.local[
static_cast<std::size_t
>(triangle.
vert[2])];
676#if !defined(MXVK_USE_EIGEN_MATH) || !defined(MXVK_OBJ_LOADER)
677 const mxvk::vec4D center(
678 (a.
x + b.
x + c.
x) * (1.0f / 3.0f),
679 (a.
y + b.
y + c.
y) * (1.0f / 3.0f),
680 (a.
z + b.
z + c.
z) * (1.0f / 3.0f),
683 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
686#if defined(MXVK_USE_EIGEN_MATH)
687 const Eigen::Index batch_index =
static_cast<Eigen::Index
>(index);
688#if !defined(MXVK_OBJ_LOADER)
689 local_face_center_batch.col(batch_index) = Eigen::Vector4f(center.x, center.y, center.z, center.w);
691 local_face_normal_batch.col(batch_index) = Eigen::Vector4f(normal.
x, normal.
y, normal.
z, normal.
w);
693 local_face_centers[index] = center;
694 local_face_normals[index] = normal;
699 void transform_and_project_vertices(
const mxvk::Mat4D &rotation) {
700#if defined(MXVK_USE_EIGEN_MATH)
701 transform_eigen_batch(rotation, local_vertex_batch, camera_vertex_batch);
702 camera_vertex_batch.row(2).array() += camera_distance;
704 const float scale =
static_cast<float>(std::min(frame_width, frame_height)) * 0.52f;
705 const float center_x =
static_cast<float>(frame_width) * 0.5f;
706 const float center_y =
static_cast<float>(frame_height) * 0.5f;
707 inverse_vertex_depth.array() =
708 camera_vertex_batch.row(2).array().max(0.001f).inverse();
709 projected_vertex_batch.row(0).array() =
710 center_x + camera_vertex_batch.row(0).array() * inverse_vertex_depth.array() * scale;
711 projected_vertex_batch.row(1).array() =
712 center_y - camera_vertex_batch.row(1).array() * inverse_vertex_depth.array() * scale;
713 projected_vertex_batch.row(2) = camera_vertex_batch.row(2);
714 projected_vertex_batch.row(3).setOnes();
716 rotation.
MulVec(model.local, camera_vertices);
717 for (std::size_t index = 0; index < model.local.size(); ++index) {
718 camera_vertices[index].z += camera_distance;
719 projected_vertices[index] = project_to_screen(camera_vertices[index], frame_width, frame_height);
724 [[nodiscard]] mxvk::vec4D projected_vertex(std::size_t index)
const {
725#if defined(MXVK_USE_EIGEN_MATH)
726 const Eigen::Index batch_index =
static_cast<Eigen::Index
>(index);
728 projected_vertex_batch(0, batch_index),
729 projected_vertex_batch(1, batch_index),
730 projected_vertex_batch(2, batch_index),
731 projected_vertex_batch(3, batch_index),
734 return projected_vertices[index];
738 void append_visible_face(
const mxvk::Triangle &triangle,
float intensity) {
739 const auto first =
static_cast<std::size_t
>(triangle.
vert[0]);
740 const auto second =
static_cast<std::size_t
>(triangle.
vert[1]);
741 const auto third =
static_cast<std::size_t
>(triangle.
vert[2]);
742 std::array<mxvk::vec2D, 3> texcoords = {
743 model.texcoords[first],
744 model.texcoords[second],
745 model.texcoords[third],
747#if defined(MXVK_OBJ_LOADER)
748 for (mxvk::vec2D &texcoord : texcoords) {
749 texcoord.y = 1.0f - texcoord.y;
752 const bool repeat_horizontal =
754 texture_repeat_enabled;
755 if (repeat_horizontal) {
758 visible_faces.push_back({
759 {projected_vertex(first), projected_vertex(second), projected_vertex(third)},
768 void build_visible_faces(
const mxvk::Mat4D &rotation_matrix,
const mxvk::vec4D &light_direction) {
769#if defined(MXVK_USE_EIGEN_MATH)
770#if !defined(MXVK_OBJ_LOADER)
771 transform_eigen_batch(rotation_matrix, local_face_center_batch, camera_face_center_batch);
772 camera_face_center_batch.row(2).array() += camera_distance;
774 transform_eigen_batch(rotation_matrix, local_face_normal_batch, camera_face_normal_batch);
775#if !defined(MXVK_OBJ_LOADER)
777 (-camera_face_normal_batch.row(0).array() * camera_face_center_batch.row(0).array() -
778 camera_face_normal_batch.row(1).array() * camera_face_center_batch.row(1).array() -
779 camera_face_normal_batch.row(2).array() * camera_face_center_batch.row(2).array()) >
784 (camera_face_normal_batch.row(0).array() * light_direction.
x +
785 camera_face_normal_batch.row(1).array() * light_direction.
y +
786 camera_face_normal_batch.row(2).array() * light_direction.
z)
788 triangle_intensity.array() = (0.35f + diffuse * 0.65f).min(1.0f);
790 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
791 const Eigen::Index batch_index =
static_cast<Eigen::Index
>(index);
792#if !defined(MXVK_OBJ_LOADER)
793 if (!triangle_visible(batch_index)) {
797 const mxvk::Triangle &triangle = model.
vlist[index];
798 append_visible_face(triangle, triangle_intensity(batch_index));
801 rotation_matrix.
MulVec(local_face_centers, camera_face_centers);
802 rotation_matrix.
MulVec(local_face_normals, camera_face_normals);
803 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
804 const mxvk::Triangle &triangle = model.
vlist[index];
805 mxvk::vec4D ¢er = camera_face_centers[index];
806 center.
z += camera_distance;
807 const mxvk::vec4D &normal = camera_face_normals[index];
808 const mxvk::vec4D view_direction(-center.
x, -center.
y, -center.
z, 0.0f);
809#if !defined(MXVK_OBJ_LOADER)
810 if (normal.
DotProduct(view_direction) <= 0.0f) {
815 const float diffuse = std::max(0.0f, normal.
DotProduct(light_direction));
816 append_visible_face(triangle, std::clamp(0.35f + diffuse * 0.65f, 0.0f, 1.0f));
821 void ensure_framebuffer() {
822 if (frame_surface !=
nullptr) {
827 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
828 if (frame_format ==
nullptr) {
829 throw mxvk::Exception(std::format(
"{}: failed to query frame format: {}", APP_NAME, SDL_GetError()));
832 depth_buffer.resize(
static_cast<std::size_t
>(frame_width) *
static_cast<std::size_t
>(frame_height));
833 clear_frame(BACKGROUND_COLOR);
834 wireframe_pipeline.Begin(frame_width, frame_height, [
this](
int x,
int y,
mxvk::MXCOLOR color) {
835 put_pixel(x, y, color);
839 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
842 [[nodiscard]] std::uint32_t map_color(
mxvk::MXCOLOR color)
const {
847 SDL_FillSurfaceRect(frame_surface.get(),
nullptr, map_color(color));
851 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
855 auto *row =
static_cast<std::uint8_t *
>(frame_surface->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_surface->pitch));
856 auto *pixel =
reinterpret_cast<std::uint32_t *
>(row) + x;
857 *pixel = map_color(color);
860 [[nodiscard]]
float texture_level_for_face(
const FaceDraw &face,
const Texture *texture,
float area)
const {
861 if (texture ==
nullptr || !mipmapping_enabled) {
865 const mxvk::vec4D &a = face.points[0];
866 const mxvk::vec4D &b = face.points[1];
867 const mxvk::vec4D &c = face.points[2];
868 const float weight_a_dx = (c.
y - b.
y) / area;
869 const float weight_a_dy = -(c.
x - b.
x) / area;
870 const float weight_b_dx = (a.
y - c.
y) / area;
871 const float weight_b_dy = -(a.
x - c.
x) / area;
872 const float weight_c_dx = (b.
y - a.
y) / area;
873 const float weight_c_dy = -(b.
x - a.
x) / area;
879 if (warp_fix_enabled) {
880 const float inverse_z_a = 1.0f / a.
z;
881 const float inverse_z_b = 1.0f / b.
z;
882 const float inverse_z_c = 1.0f / c.
z;
883 const float inverse_z = (inverse_z_a + inverse_z_b + inverse_z_c) / 3.0f;
884 const float inverse_z_dx =
885 weight_a_dx * inverse_z_a +
886 weight_b_dx * inverse_z_b +
887 weight_c_dx * inverse_z_c;
888 const float inverse_z_dy =
889 weight_a_dy * inverse_z_a +
890 weight_b_dy * inverse_z_b +
891 weight_c_dy * inverse_z_c;
892 const auto corrected_derivatives = [&](
float first,
float second,
float third) {
893 const float value_over_z =
894 (first * inverse_z_a + second * inverse_z_b + third * inverse_z_c) / 3.0f;
895 const float value_over_z_dx =
896 weight_a_dx * first * inverse_z_a +
897 weight_b_dx * second * inverse_z_b +
898 weight_c_dx * third * inverse_z_c;
899 const float value_over_z_dy =
900 weight_a_dy * first * inverse_z_a +
901 weight_b_dy * second * inverse_z_b +
902 weight_c_dy * third * inverse_z_c;
903 const float denominator = inverse_z * inverse_z;
904 return std::array<float, 2>{
905 (value_over_z_dx * inverse_z - value_over_z * inverse_z_dx) / denominator,
906 (value_over_z_dy * inverse_z - value_over_z * inverse_z_dy) / denominator,
909 const std::array<float, 2> u_derivatives = corrected_derivatives(
912 face.texcoords[2].x);
913 const std::array<float, 2> v_derivatives = corrected_derivatives(
916 face.texcoords[2].y);
917 u_dx = u_derivatives[0];
918 u_dy = u_derivatives[1];
919 v_dx = v_derivatives[0];
920 v_dy = v_derivatives[1];
923 weight_a_dx * face.texcoords[0].x +
924 weight_b_dx * face.texcoords[1].x +
925 weight_c_dx * face.texcoords[2].x;
927 weight_a_dy * face.texcoords[0].x +
928 weight_b_dy * face.texcoords[1].x +
929 weight_c_dy * face.texcoords[2].x;
931 weight_a_dx * face.texcoords[0].y +
932 weight_b_dx * face.texcoords[1].y +
933 weight_c_dx * face.texcoords[2].y;
935 weight_a_dy * face.texcoords[0].y +
936 weight_b_dy * face.texcoords[1].y +
937 weight_c_dy * face.texcoords[2].y;
940 const float horizontal_footprint = std::hypot(
941 u_dx *
static_cast<float>(texture->width()),
942 v_dx *
static_cast<float>(texture->height()));
943 const float vertical_footprint = std::hypot(
944 u_dy *
static_cast<float>(texture->width()),
945 v_dy *
static_cast<float>(texture->height()));
946 return std::log2(std::max({
mxvk::EPSILON, horizontal_footprint, vertical_footprint})) + mip_level_bias;
949 void draw_gradient_triangle(
const FaceDraw &face) {
950 const mxvk::vec4D &a = face.points[0];
951 const mxvk::vec4D &b = face.points[1];
952 const mxvk::vec4D &c = face.points[2];
953 const auto edge = [](
const mxvk::vec4D &first,
const mxvk::vec4D &second,
float x,
float y) {
954 return (x - first.
x) * (second.y - first.
y) - (y - first.
y) * (second.x - first.
x);
957 const float area = edge(b, c, a.
x, a.
y);
962 const int min_x = std::clamp(
static_cast<int>(std::floor(std::min({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
963 const int max_x = std::clamp(
static_cast<int>(std::ceil(std::max({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
964 const int min_y = std::clamp(
static_cast<int>(std::floor(std::min({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
965 const int max_y = std::clamp(
static_cast<int>(std::ceil(std::max({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
966 const Texture *texture = face_texture(face.material_index);
967 const float texture_level = texture_level_for_face(face, texture, area);
969 for (
int y = min_y; y <= max_y; ++y) {
970 for (
int x = min_x; x <= max_x; ++x) {
971 const float sample_x =
static_cast<float>(x) + 0.5f;
972 const float sample_y =
static_cast<float>(y) + 0.5f;
973 const float weight_a = edge(b, c, sample_x, sample_y) / area;
974 const float weight_b = edge(c, a, sample_x, sample_y) / area;
975 const float weight_c = edge(a, b, sample_x, sample_y) / area;
980 const float reciprocal_depth =
988 const float depth = 1.0f / reciprocal_depth;
989 const std::size_t pixel_index =
990 static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_width) +
991 static_cast<std::size_t
>(x);
992 if (depth >= depth_buffer[pixel_index]) {
995 depth_buffer[pixel_index] = depth;
997 const float texture_weight_a = warp_fix_enabled ? (weight_a / a.
z) * depth : weight_a;
998 const float texture_weight_b = warp_fix_enabled ? (weight_b / b.
z) * depth : weight_b;
999 const float texture_weight_c = warp_fix_enabled ? (weight_c / c.
z) * depth : weight_c;
1001 face.texcoords[0].x * texture_weight_a +
1002 face.texcoords[1].x * texture_weight_b +
1003 face.texcoords[2].x * texture_weight_c;
1005 face.texcoords[0].y * texture_weight_a +
1006 face.texcoords[1].y * texture_weight_b +
1007 face.texcoords[2].y * texture_weight_c;
1009 if (texture !=
nullptr) {
1010 color = texture->sample(u, v, texture_level, face.repeat_horizontal);
1012#if !defined(MXVK_OBJ_LOADER)
1014 color = gradient_color(u, v);
1022 [[nodiscard]]
static mxvk::MXCOLOR gradient_color(
float u,
float v) {
1023 u = std::clamp(u, 0.0f, 1.0f);
1024 v = std::clamp(v, 0.0f, 1.0f);
1030 const auto bilinear_channel = [&](
auto component) {
1031 const float bottom =
1032 static_cast<float>(component(BOTTOM_LEFT)) +
1033 (
static_cast<float>(component(BOTTOM_RIGHT)) -
static_cast<float>(component(BOTTOM_LEFT))) * u;
1035 static_cast<float>(component(TOP_LEFT)) +
1036 (
static_cast<float>(component(TOP_RIGHT)) -
static_cast<float>(component(TOP_LEFT))) * u;
1037 return std::clamp(
static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
1046 [[nodiscard]]
static mxvk::vec4D project_to_screen(
const mxvk::vec4D &point,
int width,
int height) {
1047 const float scale =
static_cast<float>(std::min(width, height)) * 0.52f;
1048 const float center_x =
static_cast<float>(width) * 0.5f;
1049 const float center_y =
static_cast<float>(height) * 0.5f;
1050 const float z = std::max(point.
z, 0.001f);
1051 return {center_x + (point.
x / z) * scale, center_y - (point.
y / z) * scale, point.
z, 1.0f};