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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user