264 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) :
mxvk::
VK_Window(title, width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), override_texture(wireframe ? Texture{} : load_texture(texture_filename, asset_path, !disable_mipmap)), frame_width(framebuffer.width), frame_height(framebuffer.height), fallback_width(width), fallback_height(height), warp_fix_enabled(!disable_warp_fix), mipmapping_enabled(!disable_mipmap), mip_level_bias(mip_bias), texture_repeat_enabled(repeat_texture), benchmark_enabled(benchmark), wireframe_enabled(wireframe) {
268 const std::filesystem::path path = model_path(filename, asset_path);
269#if defined(MXVK_OBJ_LOADER)
272 const mxvk::vec4D scale(MODEL_SCALE, MODEL_SCALE, MODEL_SCALE);
274#if defined(MXVK_OBJ_LOADER)
280 throw mxvk::Exception(std::format(
"{}: failed to load {} model '{}'", APP_NAME, MODEL_FORMAT, path.string()));
282#if defined(MXVK_OBJ_LOADER)
283 filter_auxiliary_objects();
285 if (!wireframe_enabled) {
286 load_material_textures(asset_path, !disable_mipmap);
289#if defined(MXVK_USE_EIGEN_MATH)
290 local_vertex_batch.resize(4,
static_cast<Eigen::Index
>(
model.local.size()));
291 camera_vertex_batch.resize(4,
static_cast<Eigen::Index
>(
model.local.size()));
292 projected_vertex_batch.resize(4,
static_cast<Eigen::Index
>(
model.local.size()));
293 inverse_vertex_depth.resize(
static_cast<Eigen::Index
>(
model.local.size()));
294#if !defined(MXVK_OBJ_LOADER)
295 local_face_center_batch.resize(4,
static_cast<Eigen::Index
>(
model.vlist.size()));
296 camera_face_center_batch.resize(4,
static_cast<Eigen::Index
>(
model.vlist.size()));
298 local_face_normal_batch.resize(4,
static_cast<Eigen::Index
>(
model.vlist.size()));
299 camera_face_normal_batch.resize(4,
static_cast<Eigen::Index
>(
model.vlist.size()));
300 triangle_intensity.resize(
static_cast<Eigen::Index
>(
model.vlist.size()));
301#if !defined(MXVK_OBJ_LOADER)
302 triangle_visible.resize(
static_cast<Eigen::Index
>(
model.vlist.size()));
304 for (std::size_t index = 0; index <
model.local.size(); ++index) {
305 local_vertex_batch.col(
static_cast<Eigen::Index
>(index)) = Eigen::Vector4f(
model.local[index].x,
model.local[index].y,
model.local[index].z,
model.local[index].w);
308 camera_vertices.resize(
model.local.size());
309 projected_vertices.resize(
model.local.size());
311 initialize_face_geometry();
312 visible_faces.reserve(
model.vlist.size());
313 std::cout << std::format(
"{}: loaded '{}' ({} vertices, {} triangles, {} materials)\n", APP_NAME, path.string(),
model.num_vertices,
model.num_polys,
model.materials.size());
317 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
320 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE && !e.key.repeat) {
321 automatic_rotation = !automatic_rotation;
324 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
325 mouse_dragging =
true;
326 last_mouse_x = e.button.x;
327 last_mouse_y = e.button.y;
330 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
331 mouse_dragging =
false;
334 if (e.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
335 mouse_dragging =
false;
338 if (e.type == SDL_EVENT_MOUSE_MOTION && mouse_dragging) {
339 const float delta_x = e.motion.x - last_mouse_x;
340 const float delta_y = e.motion.y - last_mouse_y;
341 yaw_degrees = std::fmod(yaw_degrees + delta_x * MOUSE_ROTATION_SENSITIVITY, 360.0f);
342 pitch_degrees = std::clamp(pitch_degrees + delta_y * MOUSE_ROTATION_SENSITIVITY, -MAX_PITCH_DEGREES, MAX_PITCH_DEGREES);
343 last_mouse_x = e.motion.x;
344 last_mouse_y = e.motion.y;
347 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
348 const float delta = e.wheel.y != 0.0f ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
349 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
357 ensure_framebuffer();
358 if (frame_sprite ==
nullptr || frame_surface ==
nullptr || frame_format ==
nullptr) {
362 clear_frame(BACKGROUND_COLOR);
363 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
365 const std::uint64_t current_ticks = SDL_GetTicks();
366 const float elapsed_seconds = previous_frame_ticks == 0 ? 0.0f :
static_cast<float>(current_ticks - previous_frame_ticks) * 0.001f;
367 previous_frame_ticks = current_ticks;
368 if (automatic_rotation && !mouse_dragging) {
369 yaw_degrees = std::fmod(yaw_degrees + elapsed_seconds * 42.0f, 360.0f);
372 if (benchmark_enabled && benchmark_frame_count == 0) {
373 benchmark_stopwatch = std::make_unique<StopWatch<HighResolutionClockPolicy>>(benchmark_name);
377 rotation.
BuildXYZ(pitch_degrees, yaw_degrees, 0.0f);
378 transform_and_project_vertices(rotation);
380 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
382 visible_faces.clear();
383 build_visible_faces(rotation, light_direction);
385 for (
const FaceDraw &face : visible_faces) {
386 if (wireframe_enabled) {
387 wireframe_pipeline.DrawWireframeTriangle(face.points[0], face.points[1], face.points[2],
mxvk::shade_color(face.material_color, face.intensity), depth_buffer);
389 draw_gradient_triangle(face);
393 if (benchmark_stopwatch !=
nullptr) {
394 ++benchmark_frame_count;
395 if (benchmark_frame_count == BENCHMARK_FRAME_COUNT) {
396 benchmark_stopwatch->Stop();
397 benchmark_stopwatch.reset();
402 frame_sprite->updateTexture(frame_surface->pixels, frame_width, frame_height, frame_surface->pitch);
403 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
409 Texture override_texture;
410 std::vector<Texture> material_textures;
411 SurfacePtr frame_surface;
412#if defined(MXVK_USE_EIGEN_MATH)
413 using VertexBatch = Eigen::Matrix<float, 4, Eigen::Dynamic, Eigen::RowMajor>;
414 VertexBatch local_vertex_batch;
415 VertexBatch camera_vertex_batch;
416 VertexBatch projected_vertex_batch;
417 Eigen::RowVectorXf inverse_vertex_depth;
418 VertexBatch local_face_center_batch;
419 VertexBatch camera_face_center_batch;
420 VertexBatch local_face_normal_batch;
421 VertexBatch camera_face_normal_batch;
422 Eigen::RowVectorXf triangle_intensity;
423 Eigen::Array<bool, 1, Eigen::Dynamic> triangle_visible;
425 std::vector<mxvk::vec4D> camera_vertices;
426 std::vector<mxvk::vec4D> projected_vertices;
427 std::vector<mxvk::vec4D> local_face_centers;
428 std::vector<mxvk::vec4D> camera_face_centers;
429 std::vector<mxvk::vec4D> local_face_normals;
430 std::vector<mxvk::vec4D> camera_face_normals;
432 std::vector<FaceDraw> visible_faces;
433 std::vector<float> depth_buffer;
434 const SDL_PixelFormatDetails *frame_format =
nullptr;
436 int frame_width = 1280;
437 int frame_height = 720;
438 int fallback_width = 1280;
439 int fallback_height = 720;
440 float camera_distance = 4.25f;
441 float pitch_degrees = 0.0f;
442 float yaw_degrees = 0.0f;
443 float last_mouse_x = 0.0f;
444 float last_mouse_y = 0.0f;
445 bool mouse_dragging =
false;
446 bool automatic_rotation =
true;
447 std::uint64_t previous_frame_ticks = 0;
448 bool warp_fix_enabled =
true;
449 bool mipmapping_enabled =
true;
450 float mip_level_bias = 0.0f;
451 bool texture_repeat_enabled =
false;
452 bool benchmark_enabled =
false;
453 bool wireframe_enabled =
false;
454 std::size_t benchmark_frame_count = 0;
455 std::string benchmark_name = std::format(
"{} geometry draw ({} backend, {} frames)", MODEL_FORMAT, BACKEND_NAME, BENCHMARK_FRAME_COUNT);
456 std::unique_ptr<StopWatch<HighResolutionClockPolicy>> benchmark_stopwatch;
458 static constexpr std::size_t BENCHMARK_FRAME_COUNT = 60 * 10;
460#if defined(MXVK_OBJ_LOADER)
461 void fit_model_to_view() {
462 if (
model.vlist.empty()) {
463 throw mxvk::Exception(std::format(
"{}: cannot fit an OBJ model with no triangles", APP_NAME));
466 mxvk::vec4D minimum(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
467 mxvk::vec4D maximum(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
469 for (
const int vertex_index : triangle.
vert) {
470 const mxvk::vec4D &vertex =
model.local[
static_cast<std::size_t
>(vertex_index)];
471 minimum.x = std::min(minimum.x, vertex.
x);
472 minimum.y = std::min(minimum.y, vertex.
y);
473 minimum.z = std::min(minimum.z, vertex.
z);
474 maximum.x = std::max(maximum.x, vertex.
x);
475 maximum.y = std::max(maximum.y, vertex.
y);
476 maximum.z = std::max(maximum.z, vertex.
z);
480 const mxvk::vec4D center((minimum.x + maximum.x) * 0.5f, (minimum.y + maximum.y) * 0.5f, (minimum.z + maximum.z) * 0.5f);
481 float source_radius = 0.0f;
482 for (
const mxvk::Triangle &triangle : model.vlist) {
483 for (
const int vertex_index : triangle.
vert) {
484 const mxvk::vec4D &vertex = model.local[
static_cast<std::size_t
>(vertex_index)];
485 source_radius = std::max(source_radius, std::sqrt((vertex.
x - center.x) * (vertex.
x - center.x) + (vertex.
y - center.y) * (vertex.
y - center.y) + (vertex.
z - center.z) * (vertex.
z - center.z)));
488 if (!std::isfinite(source_radius) || source_radius <=
mxvk::EPSILON) {
489 throw mxvk::Exception(std::format(
"{}: cannot fit OBJ model with degenerate bounds", APP_NAME));
492 const float fit_scale = MODEL_FIT_RADIUS / source_radius;
493 for (mxvk::vec4D &vertex : model.local) {
494 vertex.
x = (vertex.
x - center.x) * fit_scale;
495 vertex.
y = (vertex.
y - center.y) * fit_scale;
496 vertex.
z = (vertex.
z - center.z) * fit_scale;
499 std::cout << std::format(
"{}: centered model and scaled radius {:.3f} to {:.3f} (scale {:.6f})\n", APP_NAME, source_radius, MODEL_FIT_RADIUS, fit_scale);
503 void filter_auxiliary_objects() {
504 std::unordered_map<std::string, std::size_t> triangle_counts;
505 for (
const mxvk::Triangle &triangle : model.vlist) {
508 if (triangle_counts.size() <= 1) {
512 const auto dominant = std::max_element(triangle_counts.begin(), triangle_counts.end(), [](
const auto &left,
const auto &right) { return left.second < right.second; });
513 constexpr float DOMINANT_OBJECT_FRACTION = 0.95f;
514 if (
static_cast<float>(dominant->second) <
static_cast<float>(model.vlist.size()) * DOMINANT_OBJECT_FRACTION) {
518 const std::size_t original_triangle_count = model.vlist.size();
519 std::erase_if(model.vlist, [&dominant](
const mxvk::Triangle &triangle) { return triangle.source_object_name != dominant->first; });
520 model.num_polys =
static_cast<int>(model.vlist.size());
521 model.object_name = dominant->first;
522 std::cout << std::format(
"{}: selected dominant OBJ object '{}' ({} triangles); ignored {} auxiliary triangle(s)\n", APP_NAME, dominant->first, dominant->second, original_triangle_count - model.vlist.size());
525 void load_material_textures(
const std::string &asset_path,
bool generate_mipmaps) {
526 if (!override_texture.empty()) {
530 material_textures.resize(model.materials.size());
531 for (std::size_t index = 0; index < model.materials.size(); ++index) {
532 const std::string &
texture_path = model.materials[index].diffuse_map;
534 material_textures[index] =
load_texture(texture_path, asset_path, generate_mipmaps);
539 [[nodiscard]]
const Texture *face_texture(
int material_index)
const {
540 if (!override_texture.empty()) {
541 return &override_texture;
543 if (material_index >= 0 &&
static_cast<std::size_t
>(material_index) < material_textures.size() && !material_textures[
static_cast<std::size_t
>(material_index)].empty()) {
544 return &material_textures[
static_cast<std::size_t
>(material_index)];
549#if defined(MXVK_USE_EIGEN_MATH)
550 static void transform_eigen_batch(
const mxvk::Mat4D &matrix,
const VertexBatch &input, VertexBatch &output) {
551 for (
int component = 0; component < 4; ++component) {
552 output.row(component).array() = input.row(0).array() * matrix.
mat[0][component] + input.row(1).array() * matrix.
mat[1][component] + input.row(2).array() * matrix.
mat[2][component] + input.row(3).array() * matrix.
mat[3][component];
557 void initialize_face_geometry() {
558#if !defined(MXVK_USE_EIGEN_MATH)
559 local_face_centers.resize(model.vlist.size());
560 camera_face_centers.resize(model.vlist.size());
561 local_face_normals.resize(model.vlist.size());
562 camera_face_normals.resize(model.vlist.size());
564 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
565 const mxvk::Triangle &triangle = model.
vlist[index];
566 const mxvk::vec4D &a = model.local[
static_cast<std::size_t
>(triangle.
vert[0])];
567 const mxvk::vec4D &b = model.local[
static_cast<std::size_t
>(triangle.
vert[1])];
568 const mxvk::vec4D &c = model.local[
static_cast<std::size_t
>(triangle.
vert[2])];
569#if !defined(MXVK_USE_EIGEN_MATH) || !defined(MXVK_OBJ_LOADER)
570 const mxvk::vec4D center((a.
x + b.
x + c.
x) * (1.0f / 3.0f), (a.
y + b.
y + c.
y) * (1.0f / 3.0f), (a.
z + b.
z + c.
z) * (1.0f / 3.0f), 1.0f);
572 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
575#if defined(MXVK_USE_EIGEN_MATH)
576 const Eigen::Index batch_index =
static_cast<Eigen::Index
>(index);
577#if !defined(MXVK_OBJ_LOADER)
578 local_face_center_batch.col(batch_index) = Eigen::Vector4f(center.x, center.y, center.z, center.w);
580 local_face_normal_batch.col(batch_index) = Eigen::Vector4f(normal.
x, normal.
y, normal.
z, normal.
w);
582 local_face_centers[index] = center;
583 local_face_normals[index] = normal;
588 void transform_and_project_vertices(
const mxvk::Mat4D &rotation) {
589#if defined(MXVK_USE_EIGEN_MATH)
590 transform_eigen_batch(rotation, local_vertex_batch, camera_vertex_batch);
591 camera_vertex_batch.row(2).array() += camera_distance;
593 const float scale =
static_cast<float>(std::min(frame_width, frame_height)) * 0.52f;
594 const float center_x =
static_cast<float>(frame_width) * 0.5f;
595 const float center_y =
static_cast<float>(frame_height) * 0.5f;
596 inverse_vertex_depth.array() = camera_vertex_batch.row(2).array().max(0.001f).inverse();
597 projected_vertex_batch.row(0).array() = center_x + camera_vertex_batch.row(0).array() * inverse_vertex_depth.array() * scale;
598 projected_vertex_batch.row(1).array() = center_y - camera_vertex_batch.row(1).array() * inverse_vertex_depth.array() * scale;
599 projected_vertex_batch.row(2) = camera_vertex_batch.row(2);
600 projected_vertex_batch.row(3).setOnes();
602 rotation.
MulVec(model.local, camera_vertices);
603 for (std::size_t index = 0; index < model.local.size(); ++index) {
604 camera_vertices[index].z += camera_distance;
605 projected_vertices[index] = project_to_screen(camera_vertices[index], frame_width, frame_height);
610 [[nodiscard]] mxvk::vec4D projected_vertex(std::size_t index)
const {
611#if defined(MXVK_USE_EIGEN_MATH)
612 const Eigen::Index batch_index =
static_cast<Eigen::Index
>(index);
614 projected_vertex_batch(0, batch_index),
615 projected_vertex_batch(1, batch_index),
616 projected_vertex_batch(2, batch_index),
617 projected_vertex_batch(3, batch_index),
620 return projected_vertices[index];
624 void append_visible_face(
const mxvk::Triangle &triangle,
float intensity) {
625 const auto first =
static_cast<std::size_t
>(triangle.
vert[0]);
626 const auto second =
static_cast<std::size_t
>(triangle.
vert[1]);
627 const auto third =
static_cast<std::size_t
>(triangle.
vert[2]);
628 std::array<mxvk::vec2D, 3> texcoords = {
629 model.texcoords[first],
630 model.texcoords[second],
631 model.texcoords[third],
633#if defined(MXVK_OBJ_LOADER)
634 for (mxvk::vec2D &texcoord : texcoords) {
635 texcoord.y = 1.0f - texcoord.y;
638 const bool repeat_horizontal = face_texture(triangle.
material_index) !=
nullptr && texture_repeat_enabled;
639 if (repeat_horizontal) {
642 visible_faces.push_back({
643 {projected_vertex(first), projected_vertex(second), projected_vertex(third)},
652 void build_visible_faces(
const mxvk::Mat4D &rotation_matrix,
const mxvk::vec4D &light_direction) {
653#if defined(MXVK_USE_EIGEN_MATH)
654#if !defined(MXVK_OBJ_LOADER)
655 transform_eigen_batch(rotation_matrix, local_face_center_batch, camera_face_center_batch);
656 camera_face_center_batch.row(2).array() += camera_distance;
658 transform_eigen_batch(rotation_matrix, local_face_normal_batch, camera_face_normal_batch);
659#if !defined(MXVK_OBJ_LOADER)
660 triangle_visible = (-camera_face_normal_batch.row(0).array() * camera_face_center_batch.row(0).array() - camera_face_normal_batch.row(1).array() * camera_face_center_batch.row(1).array() - camera_face_normal_batch.row(2).array() * camera_face_center_batch.row(2).array()) > 0.0f;
663 const auto diffuse = (camera_face_normal_batch.row(0).array() * light_direction.
x + camera_face_normal_batch.row(1).array() * light_direction.
y + camera_face_normal_batch.row(2).array() * light_direction.
z).max(0.0f);
664 triangle_intensity.array() = (0.35f + diffuse * 0.65f).min(1.0f);
666 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
667 const Eigen::Index batch_index =
static_cast<Eigen::Index
>(index);
668#if !defined(MXVK_OBJ_LOADER)
669 if (!triangle_visible(batch_index)) {
673 const mxvk::Triangle &triangle = model.
vlist[index];
674 append_visible_face(triangle, triangle_intensity(batch_index));
677 rotation_matrix.
MulVec(local_face_centers, camera_face_centers);
678 rotation_matrix.
MulVec(local_face_normals, camera_face_normals);
679 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
680 const mxvk::Triangle &triangle = model.
vlist[index];
681 mxvk::vec4D ¢er = camera_face_centers[index];
682 center.
z += camera_distance;
683 const mxvk::vec4D &normal = camera_face_normals[index];
684 const mxvk::vec4D view_direction(-center.
x, -center.
y, -center.
z, 0.0f);
685#if !defined(MXVK_OBJ_LOADER)
686 if (normal.
DotProduct(view_direction) <= 0.0f) {
691 const float diffuse = std::max(0.0f, normal.
DotProduct(light_direction));
692 append_visible_face(triangle, std::clamp(0.35f + diffuse * 0.65f, 0.0f, 1.0f));
697 void ensure_framebuffer() {
698 if (frame_surface !=
nullptr) {
703 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
704 if (frame_format ==
nullptr) {
705 throw mxvk::Exception(std::format(
"{}: failed to query frame format: {}", APP_NAME, SDL_GetError()));
708 depth_buffer.resize(
static_cast<std::size_t
>(frame_width) *
static_cast<std::size_t
>(frame_height));
709 clear_frame(BACKGROUND_COLOR);
710 wireframe_pipeline.Begin(frame_width, frame_height, [
this](
int x,
int y,
mxvk::MXCOLOR color) { put_pixel(x, y, color); });
713 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
718 void clear_frame(
mxvk::MXCOLOR color) { SDL_FillSurfaceRect(frame_surface.get(),
nullptr, map_color(color)); }
721 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
725 auto *row =
static_cast<std::uint8_t *
>(frame_surface->pixels) + (
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_surface->pitch));
726 auto *pixel =
reinterpret_cast<std::uint32_t *
>(row) + x;
727 *pixel = map_color(color);
730 [[nodiscard]]
float texture_level_for_face(
const FaceDraw &face,
const Texture *texture,
float area)
const {
731 if (texture ==
nullptr || !mipmapping_enabled) {
735 const mxvk::vec4D &a = face.points[0];
736 const mxvk::vec4D &b = face.points[1];
737 const mxvk::vec4D &c = face.points[2];
738 const float weight_a_dx = (c.
y - b.
y) / area;
739 const float weight_a_dy = -(c.
x - b.
x) / area;
740 const float weight_b_dx = (a.
y - c.
y) / area;
741 const float weight_b_dy = -(a.
x - c.
x) / area;
742 const float weight_c_dx = (b.
y - a.
y) / area;
743 const float weight_c_dy = -(b.
x - a.
x) / area;
749 if (warp_fix_enabled) {
750 const float inverse_z_a = 1.0f / a.
z;
751 const float inverse_z_b = 1.0f / b.
z;
752 const float inverse_z_c = 1.0f / c.
z;
753 const float inverse_z = (inverse_z_a + inverse_z_b + inverse_z_c) / 3.0f;
754 const float inverse_z_dx = weight_a_dx * inverse_z_a + weight_b_dx * inverse_z_b + weight_c_dx * inverse_z_c;
755 const float inverse_z_dy = weight_a_dy * inverse_z_a + weight_b_dy * inverse_z_b + weight_c_dy * inverse_z_c;
756 const auto corrected_derivatives = [&](
float first,
float second,
float third) {
757 const float value_over_z = (first * inverse_z_a + second * inverse_z_b + third * inverse_z_c) / 3.0f;
758 const float value_over_z_dx = weight_a_dx * first * inverse_z_a + weight_b_dx * second * inverse_z_b + weight_c_dx * third * inverse_z_c;
759 const float value_over_z_dy = weight_a_dy * first * inverse_z_a + weight_b_dy * second * inverse_z_b + weight_c_dy * third * inverse_z_c;
760 const float denominator = inverse_z * inverse_z;
761 return std::array<float, 2>{
762 (value_over_z_dx * inverse_z - value_over_z * inverse_z_dx) / denominator,
763 (value_over_z_dy * inverse_z - value_over_z * inverse_z_dy) / denominator,
766 const std::array<float, 2> u_derivatives = corrected_derivatives(face.texcoords[0].x, face.texcoords[1].x, face.texcoords[2].x);
767 const std::array<float, 2> v_derivatives = corrected_derivatives(face.texcoords[0].y, face.texcoords[1].y, face.texcoords[2].y);
768 u_dx = u_derivatives[0];
769 u_dy = u_derivatives[1];
770 v_dx = v_derivatives[0];
771 v_dy = v_derivatives[1];
773 u_dx = weight_a_dx * face.texcoords[0].x + weight_b_dx * face.texcoords[1].x + weight_c_dx * face.texcoords[2].x;
774 u_dy = weight_a_dy * face.texcoords[0].x + weight_b_dy * face.texcoords[1].x + weight_c_dy * face.texcoords[2].x;
775 v_dx = weight_a_dx * face.texcoords[0].y + weight_b_dx * face.texcoords[1].y + weight_c_dx * face.texcoords[2].y;
776 v_dy = weight_a_dy * face.texcoords[0].y + weight_b_dy * face.texcoords[1].y + weight_c_dy * face.texcoords[2].y;
779 const float horizontal_footprint = std::hypot(u_dx *
static_cast<float>(texture->width()), v_dx *
static_cast<float>(texture->height()));
780 const float vertical_footprint = std::hypot(u_dy *
static_cast<float>(texture->width()), v_dy *
static_cast<float>(texture->height()));
781 return std::log2(std::max({
mxvk::EPSILON, horizontal_footprint, vertical_footprint})) + mip_level_bias;
784 void draw_gradient_triangle(
const FaceDraw &face) {
785 const mxvk::vec4D &a = face.points[0];
786 const mxvk::vec4D &b = face.points[1];
787 const mxvk::vec4D &c = face.points[2];
788 const auto edge = [](
const mxvk::vec4D &first,
const mxvk::vec4D &second,
float x,
float y) {
return (x - first.
x) * (second.y - first.
y) - (y - first.
y) * (second.x - first.
x); };
790 const float area = edge(b, c, a.
x, a.
y);
795 const int min_x = std::clamp(
static_cast<int>(std::floor(std::min({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
796 const int max_x = std::clamp(
static_cast<int>(std::ceil(std::max({a.
x, b.
x, c.
x}))), 0, frame_width - 1);
797 const int min_y = std::clamp(
static_cast<int>(std::floor(std::min({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
798 const int max_y = std::clamp(
static_cast<int>(std::ceil(std::max({a.
y, b.
y, c.
y}))), 0, frame_height - 1);
799 const Texture *texture = face_texture(face.material_index);
800 const float texture_level = texture_level_for_face(face, texture, area);
802 for (
int y = min_y; y <= max_y; ++y) {
803 for (
int x = min_x; x <= max_x; ++x) {
804 const float sample_x =
static_cast<float>(x) + 0.5f;
805 const float sample_y =
static_cast<float>(y) + 0.5f;
806 const float weight_a = edge(b, c, sample_x, sample_y) / area;
807 const float weight_b = edge(c, a, sample_x, sample_y) / area;
808 const float weight_c = edge(a, b, sample_x, sample_y) / area;
813 const float reciprocal_depth = weight_a / a.
z + weight_b / b.
z + weight_c / c.
z;
818 const float depth = 1.0f / reciprocal_depth;
819 const std::size_t pixel_index =
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(frame_width) +
static_cast<std::size_t
>(x);
820 if (depth >= depth_buffer[pixel_index]) {
823 depth_buffer[pixel_index] = depth;
825 const float texture_weight_a = warp_fix_enabled ? (weight_a / a.
z) * depth : weight_a;
826 const float texture_weight_b = warp_fix_enabled ? (weight_b / b.
z) * depth : weight_b;
827 const float texture_weight_c = warp_fix_enabled ? (weight_c / c.
z) * depth : weight_c;
828 const float u = face.texcoords[0].x * texture_weight_a + face.texcoords[1].x * texture_weight_b + face.texcoords[2].x * texture_weight_c;
829 const float v = face.texcoords[0].y * texture_weight_a + face.texcoords[1].y * texture_weight_b + face.texcoords[2].y * texture_weight_c;
831 if (texture !=
nullptr) {
832 color = texture->sample(u, v, texture_level, face.repeat_horizontal);
834#if !defined(MXVK_OBJ_LOADER)
836 color = gradient_color(u, v);
844 [[nodiscard]]
static mxvk::MXCOLOR gradient_color(
float u,
float v) {
845 u = std::clamp(u, 0.0f, 1.0f);
846 v = std::clamp(v, 0.0f, 1.0f);
852 const auto bilinear_channel = [&](
auto component) {
853 const float bottom =
static_cast<float>(component(BOTTOM_LEFT)) + (
static_cast<float>(component(BOTTOM_RIGHT)) -
static_cast<float>(component(BOTTOM_LEFT))) * u;
854 const float top =
static_cast<float>(component(TOP_LEFT)) + (
static_cast<float>(component(TOP_RIGHT)) -
static_cast<float>(component(TOP_LEFT))) * u;
855 return std::clamp(
static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
861 [[nodiscard]]
static mxvk::vec4D project_to_screen(
const mxvk::vec4D &point,
int width,
int height) {
862 const float scale =
static_cast<float>(std::min(width, height)) * 0.52f;
863 const float center_x =
static_cast<float>(width) * 0.5f;
864 const float center_y =
static_cast<float>(height) * 0.5f;
865 const float z = std::max(point.
z, 0.001f);
866 return {center_x + (point.
x / z) * scale, center_y - (point.
y / z) * scale, point.
z, 1.0f};