OBJ: improve UV values merging in the exporter

Previous code was using BKE_mesh_uv_vert_map_get_vert to somewhat
detect identical UV values on mesh vertices. But this was not
handling the case when separate mesh vertices still use the same UV
values (e.g. cube with all six faces mapped to whole image).

Replace usage of BKE_mesh_uv_vert_map_get_vert with a simple "unique UV
value" map, very similar to how OBJ normals are calculated for export.

Measurements on a somewhat extreme case: exporting "Rungholt" Minecraft
level from https://casual-effects.com/data time (Win10, Ryzen 5950X)
goes 2.9sec -> 2.2sec, and resulting file size 486MB -> 231MB. This
particular model has a lot of mesh faces mapping to the same places
in UV map.

On less extreme cases the timings are similar between old and new
code, with a tiny speedup in most tests I've tried.
This commit is contained in:
Aras Pranckevicius
2023-03-27 21:38:52 +03:00
parent 8ad0935ed6
commit 05a63e3705
3 changed files with 36 additions and 50 deletions

View File

@@ -89,7 +89,7 @@ void OBJMesh::clear()
owned_export_mesh_ = nullptr;
}
export_mesh_ = nullptr;
uv_indices_.clear_and_shrink();
loop_to_uv_index_.clear_and_shrink();
uv_coords_.clear_and_shrink();
loop_to_normal_index_.clear_and_shrink();
normal_coords_.clear_and_shrink();
@@ -162,7 +162,7 @@ int OBJMesh::tot_polygons() const
int OBJMesh::tot_uv_vertices() const
{
return tot_uv_vertices_;
return int(uv_coords_.size());
}
int OBJMesh::tot_edges() const
@@ -290,71 +290,47 @@ Span<int> OBJMesh::calc_poly_vertex_indices(const int poly_index) const
void OBJMesh::store_uv_coords_and_indices()
{
const int totvert = export_mesh_->totvert;
const StringRef active_uv_name = CustomData_get_active_layer_name(&export_mesh_->ldata,
CD_PROP_FLOAT2);
if (active_uv_name.is_empty()) {
tot_uv_vertices_ = 0;
uv_coords_.clear();
return;
}
const bke::AttributeAccessor attributes = export_mesh_->attributes();
const VArraySpan<float2> uv_map = attributes.lookup<float2>(active_uv_name, ATTR_DOMAIN_CORNER);
if (uv_map.is_empty()) {
tot_uv_vertices_ = 0;
uv_coords_.clear();
return;
}
const float limit[2] = {STD_UV_CONNECT_LIMIT, STD_UV_CONNECT_LIMIT};
Map<float2, int> uv_to_index;
UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create(
mesh_polys_.data(),
nullptr,
nullptr,
mesh_corner_verts_.data(),
reinterpret_cast<const float(*)[2]>(uv_map.data()),
mesh_polys_.size(),
totvert,
limit,
false,
false);
/* We don't know how many unique UVs there will be, but this is a guess. */
uv_to_index.reserve(export_mesh_->totvert);
uv_coords_.reserve(export_mesh_->totvert);
uv_indices_.resize(mesh_polys_.size());
/* At least total vertices of a mesh will be present in its texture map. So
* reserve minimum space early. */
uv_coords_.reserve(totvert);
loop_to_uv_index_.resize(uv_map.size());
tot_uv_vertices_ = 0;
for (int vertex_index = 0; vertex_index < totvert; vertex_index++) {
const UvMapVert *uv_vert = BKE_mesh_uv_vert_map_get_vert(uv_vert_map, vertex_index);
for (; uv_vert; uv_vert = uv_vert->next) {
if (uv_vert->separate) {
tot_uv_vertices_ += 1;
}
const int verts_in_poly = mesh_polys_[uv_vert->poly_index].totloop;
/* Store UV vertex coordinates. */
uv_coords_.resize(tot_uv_vertices_);
const int loopstart = mesh_polys_[uv_vert->poly_index].loopstart;
Span<float> vert_uv_coords(uv_map[loopstart + uv_vert->loop_of_poly_index], 2);
uv_coords_[tot_uv_vertices_ - 1] = float2(vert_uv_coords[0], vert_uv_coords[1]);
/* Store UV vertex indices. */
uv_indices_[uv_vert->poly_index].resize(verts_in_poly);
/* Keep indices zero-based and let the writer handle the "+ 1" as per OBJ spec. */
uv_indices_[uv_vert->poly_index][uv_vert->loop_of_poly_index] = tot_uv_vertices_ - 1;
for (int index = 0; index < int(uv_map.size()); index++) {
float2 uv = uv_map[index];
int uv_index = uv_to_index.lookup_default(uv, -1);
if (uv_index == -1) {
uv_index = uv_to_index.size();
uv_to_index.add(uv, uv_index);
uv_coords_.append(uv);
}
loop_to_uv_index_[index] = uv_index;
}
BKE_mesh_uv_vert_map_free(uv_vert_map);
}
Span<int> OBJMesh::calc_poly_uv_indices(const int poly_index) const
{
if (uv_indices_.size() <= 0) {
if (uv_coords_.is_empty()) {
return {};
}
BLI_assert(poly_index < export_mesh_->totpoly);
BLI_assert(poly_index < uv_indices_.size());
return uv_indices_[poly_index];
const MPoly &poly = mesh_polys_[poly_index];
return loop_to_uv_index_.as_span().slice(poly.loopstart, poly.totloop);
}
float3 OBJMesh::calc_poly_normal(const int poly_index) const

View File

@@ -52,17 +52,14 @@ class OBJMesh : NonCopyable {
bool mirrored_transform_;
/**
* Total UV vertices in a mesh's texture map.
* Per-loop UV index.
*/
int tot_uv_vertices_ = 0;
/**
* Per-polygon-per-vertex UV vertex indices.
*/
Vector<Vector<int>> uv_indices_;
Vector<int> loop_to_uv_index_;
/*
* UV vertices.
*/
Vector<float2> uv_coords_;
/**
* Per-loop normal index.
*/

View File

@@ -340,6 +340,19 @@ TEST_F(obj_exporter_regression_test, edges)
}
TEST_F(obj_exporter_regression_test, vertices)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = IO_AXIS_Y;
_export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
compare_obj_export_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR
"cube_loose_edges_verts.blend",
"io_tests" SEP_STR "obj" SEP_STR "cube_loose_edges_verts.obj",
"",
_export.params);
}
TEST_F(obj_exporter_regression_test, cube_loose_edges)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = IO_AXIS_Y;