From dff9ac163ab4b751671f6375fed33119f550b42d Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sun, 27 Oct 2024 12:31:41 -0400 Subject: [PATCH] [custom levels] A few bug fixes (#3736) - Bug fix to KD tree splitting, should fix cases with bad vertex colors/alphas. - Normalize normals instead of asserting if they are the wrong length. **the fact that blender exports normals incorrectly is a bug and I doubt their implementation is correct if you've scaled things on only on axis.** - Automatically resize metallic texture (envmap strength) if it doesn't match the size of the rgb texture instead of asserting Co-authored-by: water111 --- common/CMakeLists.txt | 1 + common/util/gltf_util.cpp | 3 +- common/util/image_resize.cpp | 101 ++++++++++++++++++ common/util/image_resize.h | 12 +++ .../build_level/common/color_quantization.cpp | 23 +++- .../build_level/common/gltf_mesh_extract.cpp | 24 +++-- test/CMakeLists.txt | 3 + test/common/test_image_resize.cpp | 37 +++++++ 8 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 common/util/image_resize.cpp create mode 100644 common/util/image_resize.h create mode 100644 test/common/test_image_resize.cpp diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 46dc0cfea..c71cd1543 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -95,6 +95,7 @@ add_library(common util/Timer.cpp util/unicode_util.cpp util/gltf_util.cpp + util/image_resize.cpp versions/versions.cpp ) diff --git a/common/util/gltf_util.cpp b/common/util/gltf_util.cpp index ea29f9543..105a05641 100644 --- a/common/util/gltf_util.cpp +++ b/common/util/gltf_util.cpp @@ -323,7 +323,8 @@ ExtractedVertices gltf_vertices(const tinygltf::Model& model, normals = extract_vec3f(data_ptr, count, byte_stride); for (auto& nrm : normals) { math::Vector4f nrm4(nrm.x(), nrm.y(), nrm.z(), 0.f); - nrm = (w_T_local * nrm4).xyz(); + // we found that normals aren't normalized if an object is scaled in blender. + nrm = (w_T_local * nrm4).xyz().normalized(); } ASSERT(normals.size() == result.size()); } else { diff --git a/common/util/image_resize.cpp b/common/util/image_resize.cpp new file mode 100644 index 000000000..49ace9160 --- /dev/null +++ b/common/util/image_resize.cpp @@ -0,0 +1,101 @@ +#include "image_resize.h" + +#include + +#include "common/math/Vector.h" + +namespace { +struct BilinearSample { + int i0, i1; + double w0, w1; +}; + +BilinearSample bilinear(int src_i, double sample, bool wrap) { + BilinearSample result; + + const double px_size = 1. / double(src_i); + const double px_center_f = (sample - 0.5 * px_size) / px_size; + const int px_floor = std::floor(px_center_f); + const double frac = px_center_f - double(px_floor); + + if (px_center_f < 0) { // off the bottom + if (wrap) { + // wrap around! + result.i0 = 0; + result.i1 = src_i - 1; + result.w1 = -px_center_f; + result.w0 = 1. - result.w1; + } else { + // clamp to edge + result.i0 = 0; + result.i1 = 0; + result.w0 = 1; + result.w1 = 0; + } + } else if (px_floor >= (src_i - 1)) { // off the top + if (wrap) { + result.i0 = src_i - 1; + result.i1 = 0; + result.w0 = 1. - frac; + result.w1 = frac; + } else { + // clamp + result.i0 = src_i - 1; + result.i1 = src_i - 1; + result.w0 = 1; + result.w1 = 0; + } + } else { + result.i0 = px_floor; + result.i1 = px_floor + 1; + result.w0 = 1. - frac; + result.w1 = frac; + } + + return result; +} + +math::Vector4 sample1(const u8* src, int w, int h, int x, int y) { + (void)h; + int offset = 4 * (y * w + x); + return math::Vector4(src[offset], src[offset + 1], src[offset + 2], src[offset + 3]); +} + +math::Vector4 sample_bilinear(const u8* src, + int w, + int h, + const BilinearSample& x, + const BilinearSample& y) { + auto p00 = sample1(src, w, h, x.i0, y.i0).cast(); + auto p01 = sample1(src, w, h, x.i0, y.i1).cast(); + auto p10 = sample1(src, w, h, x.i1, y.i0).cast(); + auto p11 = sample1(src, w, h, x.i1, y.i1).cast(); + auto p_interp = (p00 * x.w0 * y.w0 + p01 * x.w0 * y.w1 + p10 * x.w1 * y.w0 + p11 * x.w1 * y.w1); + return p_interp.cast(); +} +} // namespace + +void resize_rgba_image(u8* dst, + int dst_w, + int dst_h, + const u8* src, + int src_w, + int src_h, + bool wrap_w, + bool wrap_h) { + const double dst_px_h = 1. / dst_h; + const double dst_px_w = 1. / dst_w; + for (int h = 0; h < dst_h; h++) { + const float h_pos = (double(h) + 0.5) * dst_px_h; + const auto h_sample = bilinear(src_h, h_pos, wrap_h); + for (int w = 0; w < dst_w; w++) { + const float w_pos = (double(w) + 0.5) * dst_px_w; + const auto w_sample = bilinear(src_w, w_pos, wrap_w); + auto result = sample_bilinear(src, src_w, src_h, w_sample, h_sample); + for (int i = 0; i < 4; i++) { + *dst = result[i]; + dst++; + } + } + } +} diff --git a/common/util/image_resize.h b/common/util/image_resize.h new file mode 100644 index 000000000..f6430da70 --- /dev/null +++ b/common/util/image_resize.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common/common_types.h" + +void resize_rgba_image(u8* dst, + int dst_w, + int dst_h, + const u8* src, + int src_w, + int src_h, + bool wrap_w, + bool wrap_h); \ No newline at end of file diff --git a/goalc/build_level/common/color_quantization.cpp b/goalc/build_level/common/color_quantization.cpp index 7c80b3b93..ccedb3b88 100644 --- a/goalc/build_level/common/color_quantization.cpp +++ b/goalc/build_level/common/color_quantization.cpp @@ -237,6 +237,24 @@ void split_kd(KdNode* in, u32 depth, int next_split_dim) { return; } + if (!in->colors.empty()) { + for (int i = 0; i < 4; i++) { + bool all_same = true; + u8 same = in->colors[0][next_split_dim]; + for (auto& color : in->colors) { + if (color[next_split_dim] != same) { + all_same = false; + break; + } + } + if (all_same) { + next_split_dim = (next_split_dim + 1) % 4; + } else { + break; + } + } + } + // sort by split dimension std::stable_sort(in->colors.begin(), in->colors.end(), [=](const Color& a, const Color& b) { return a[next_split_dim] < b[next_split_dim]; @@ -248,11 +266,12 @@ void split_kd(KdNode* in, u32 depth, int next_split_dim) { size_t i = 0; size_t mid = in->colors.size() / 2; if (depth & 1) { - while (mid > 1 && in->colors[mid] == in->colors[mid - 1]) { + while (mid > 1 && in->colors[mid][next_split_dim] == in->colors[mid - 1][next_split_dim]) { mid--; } } else { - while (mid + 2 < in->colors.size() && in->colors[mid] == in->colors[mid + 1]) { + while (mid + 2 < in->colors.size() && + in->colors[mid][next_split_dim] == in->colors[mid + 1][next_split_dim]) { mid++; } } diff --git a/goalc/build_level/common/gltf_mesh_extract.cpp b/goalc/build_level/common/gltf_mesh_extract.cpp index 44ba40164..3e4d0efa7 100644 --- a/goalc/build_level/common/gltf_mesh_extract.cpp +++ b/goalc/build_level/common/gltf_mesh_extract.cpp @@ -12,6 +12,7 @@ #include "common/math/geometry.h" #include "common/util/Timer.h" #include "common/util/gltf_util.h" +#include using namespace gltf_util; namespace gltf_mesh_extract { @@ -262,7 +263,9 @@ void add_to_packed_verts(std::vector* out, int texture_pool_add_envmap_control_texture(TexturePool* pool, const tinygltf::Model& model, int rgb_image_id, - int mr_image_id) { + int mr_image_id, + bool wrap_w, + bool wrap_h) { const auto& existing = pool->envmap_textures_by_gltf_id.find({rgb_image_id, mr_image_id}); if (existing != pool->envmap_textures_by_gltf_id.end()) { lg::info("Reusing envmap textures"); @@ -279,8 +282,16 @@ int texture_pool_add_envmap_control_texture(TexturePool* pool, ASSERT(mr_tex.pixel_type == TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE); ASSERT(mr_tex.component == 4); - ASSERT(rgb_tex.width == mr_tex.width); - ASSERT(rgb_tex.height == mr_tex.height); + std::vector resized_mr_tex; + const u8* mr_src; + if (rgb_tex.width == mr_tex.width && rgb_tex.height == mr_tex.height) { + mr_src = mr_tex.image.data(); + } else { + resized_mr_tex.resize(rgb_tex.width * rgb_tex.height * 4); + resize_rgba_image(resized_mr_tex.data(), rgb_tex.width, rgb_tex.height, mr_tex.image.data(), + mr_tex.width, mr_tex.height, wrap_w, wrap_h); + mr_src = resized_mr_tex.data(); + } size_t idx = pool->textures_by_idx.size(); pool->envmap_textures_by_gltf_id[{rgb_image_id, mr_image_id}] = idx; @@ -298,7 +309,7 @@ int texture_pool_add_envmap_control_texture(TexturePool* pool, // adjust alpha from metallic channel for (size_t i = 0; i < tt.data.size(); i++) { u32 rgb = tt.data[i]; - u32 metal = mr_tex.image[4 * i + 2] / 4; + u32 metal = mr_src[4 * i + 2] / 4; rgb &= 0xff'ff'ff; rgb |= (metal << 24); tt.data[i] = rgb; @@ -404,8 +415,9 @@ void extract(const Input& in, ASSERT(roughness_tex.source >= 0); // draw.tree_tex_id = texture_pool_add_texture(in.tex_pool, model.images[base_tex.source]); - draw.tree_tex_id = texture_pool_add_envmap_control_texture(in.tex_pool, model, base_tex.source, - roughness_tex.source); + draw.tree_tex_id = texture_pool_add_envmap_control_texture( + in.tex_pool, model, base_tex.source, roughness_tex.source, !draw.mode.get_clamp_s_enable(), + !draw.mode.get_clamp_t_enable()); out.base_draws.push_back(draw); // now, setup envmap draw: diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 112d57514..1eaf63f62 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,6 +39,9 @@ add_executable(goalc-test target_link_libraries(goalc-test common runtime compiler gtest decomp Zydis libzstd_static tree-sitter) +add_executable(test_image_resize ${CMAKE_CURRENT_LIST_DIR}/common/test_image_resize.cpp) +target_link_libraries(test_image_resize common) + if(WIN32) target_link_libraries(goalc-test mman) endif() diff --git a/test/common/test_image_resize.cpp b/test/common/test_image_resize.cpp new file mode 100644 index 000000000..bbf67d48b --- /dev/null +++ b/test/common/test_image_resize.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "common/common_types.h" +#include "common/util/FileUtil.h" +#include "common/util/Timer.h" +#include "common/util/image_resize.h" + +int main() { + int src_h = 30; + int src_w = 30; + int check_divide = 3; + int dst_sz = 300; + + std::vector src; + for (int h = 0; h < src_h; h++) { + for (int w = 0; w < src_w; w++) { + u8 color = (((h / check_divide) & 1) ^ ((w / check_divide) & 1)) ? 0x10 : 0xd0; + src.push_back(color); + src.push_back(color); + src.push_back(color); + src.push_back(255); + } + } + + std::vector dst(dst_sz * dst_sz * 4); + Timer timer; + resize_rgba_image(dst.data(), dst_sz, dst_sz, src.data(), src_w, src_h, true, true); + printf("resized in %.3f ms\n", timer.getMs()); + file_util::write_rgba_png("test_wrap.png", dst.data(), dst_sz, dst_sz); + resize_rgba_image(dst.data(), dst_sz, dst_sz, src.data(), src_w, src_h, false, false); + printf("resized in %.3f ms\n", timer.getMs()); + file_util::write_rgba_png("test_unwrap.png", dst.data(), dst_sz, dst_sz); + file_util::write_rgba_png("src.png", src.data(), src_w, src_h); + + return 0; +} \ No newline at end of file