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;
|
owned_export_mesh_ = nullptr;
|
||||||
}
|
}
|
||||||
export_mesh_ = nullptr;
|
export_mesh_ = nullptr;
|
||||||
uv_indices_.clear_and_shrink();
|
loop_to_uv_index_.clear_and_shrink();
|
||||||
uv_coords_.clear_and_shrink();
|
uv_coords_.clear_and_shrink();
|
||||||
loop_to_normal_index_.clear_and_shrink();
|
loop_to_normal_index_.clear_and_shrink();
|
||||||
normal_coords_.clear_and_shrink();
|
normal_coords_.clear_and_shrink();
|
||||||
@@ -162,7 +162,7 @@ int OBJMesh::tot_polygons() const
|
|||||||
|
|
||||||
int OBJMesh::tot_uv_vertices() const
|
int OBJMesh::tot_uv_vertices() const
|
||||||
{
|
{
|
||||||
return tot_uv_vertices_;
|
return int(uv_coords_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
int OBJMesh::tot_edges() const
|
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()
|
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,
|
const StringRef active_uv_name = CustomData_get_active_layer_name(&export_mesh_->ldata,
|
||||||
CD_PROP_FLOAT2);
|
CD_PROP_FLOAT2);
|
||||||
if (active_uv_name.is_empty()) {
|
if (active_uv_name.is_empty()) {
|
||||||
tot_uv_vertices_ = 0;
|
uv_coords_.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bke::AttributeAccessor attributes = export_mesh_->attributes();
|
const bke::AttributeAccessor attributes = export_mesh_->attributes();
|
||||||
const VArraySpan<float2> uv_map = attributes.lookup<float2>(active_uv_name, ATTR_DOMAIN_CORNER);
|
const VArraySpan<float2> uv_map = attributes.lookup<float2>(active_uv_name, ATTR_DOMAIN_CORNER);
|
||||||
if (uv_map.is_empty()) {
|
if (uv_map.is_empty()) {
|
||||||
tot_uv_vertices_ = 0;
|
uv_coords_.clear();
|
||||||
return;
|
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(
|
/* We don't know how many unique UVs there will be, but this is a guess. */
|
||||||
mesh_polys_.data(),
|
uv_to_index.reserve(export_mesh_->totvert);
|
||||||
nullptr,
|
uv_coords_.reserve(export_mesh_->totvert);
|
||||||
nullptr,
|
|
||||||
mesh_corner_verts_.data(),
|
|
||||||
reinterpret_cast<const float(*)[2]>(uv_map.data()),
|
|
||||||
mesh_polys_.size(),
|
|
||||||
totvert,
|
|
||||||
limit,
|
|
||||||
false,
|
|
||||||
false);
|
|
||||||
|
|
||||||
uv_indices_.resize(mesh_polys_.size());
|
loop_to_uv_index_.resize(uv_map.size());
|
||||||
/* At least total vertices of a mesh will be present in its texture map. So
|
|
||||||
* reserve minimum space early. */
|
|
||||||
uv_coords_.reserve(totvert);
|
|
||||||
|
|
||||||
tot_uv_vertices_ = 0;
|
for (int index = 0; index < int(uv_map.size()); index++) {
|
||||||
for (int vertex_index = 0; vertex_index < totvert; vertex_index++) {
|
float2 uv = uv_map[index];
|
||||||
const UvMapVert *uv_vert = BKE_mesh_uv_vert_map_get_vert(uv_vert_map, vertex_index);
|
int uv_index = uv_to_index.lookup_default(uv, -1);
|
||||||
for (; uv_vert; uv_vert = uv_vert->next) {
|
if (uv_index == -1) {
|
||||||
if (uv_vert->separate) {
|
uv_index = uv_to_index.size();
|
||||||
tot_uv_vertices_ += 1;
|
uv_to_index.add(uv, uv_index);
|
||||||
}
|
uv_coords_.append(uv);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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
|
Span<int> OBJMesh::calc_poly_uv_indices(const int poly_index) const
|
||||||
{
|
{
|
||||||
if (uv_indices_.size() <= 0) {
|
if (uv_coords_.is_empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
BLI_assert(poly_index < export_mesh_->totpoly);
|
BLI_assert(poly_index < export_mesh_->totpoly);
|
||||||
BLI_assert(poly_index < uv_indices_.size());
|
const MPoly &poly = mesh_polys_[poly_index];
|
||||||
return uv_indices_[poly_index];
|
return loop_to_uv_index_.as_span().slice(poly.loopstart, poly.totloop);
|
||||||
}
|
}
|
||||||
|
|
||||||
float3 OBJMesh::calc_poly_normal(const int poly_index) const
|
float3 OBJMesh::calc_poly_normal(const int poly_index) const
|
||||||
|
|||||||
@@ -52,17 +52,14 @@ class OBJMesh : NonCopyable {
|
|||||||
bool mirrored_transform_;
|
bool mirrored_transform_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total UV vertices in a mesh's texture map.
|
* Per-loop UV index.
|
||||||
*/
|
*/
|
||||||
int tot_uv_vertices_ = 0;
|
Vector<int> loop_to_uv_index_;
|
||||||
/**
|
|
||||||
* Per-polygon-per-vertex UV vertex indices.
|
|
||||||
*/
|
|
||||||
Vector<Vector<int>> uv_indices_;
|
|
||||||
/*
|
/*
|
||||||
* UV vertices.
|
* UV vertices.
|
||||||
*/
|
*/
|
||||||
Vector<float2> uv_coords_;
|
Vector<float2> uv_coords_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-loop normal index.
|
* Per-loop normal index.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -340,6 +340,19 @@ TEST_F(obj_exporter_regression_test, edges)
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(obj_exporter_regression_test, vertices)
|
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;
|
OBJExportParamsDefault _export;
|
||||||
_export.params.forward_axis = IO_AXIS_Y;
|
_export.params.forward_axis = IO_AXIS_Y;
|
||||||
|
|||||||
Reference in New Issue
Block a user