OBJ: Use mesh normals domain properly for normals storage #117522

Merged
Hans Goudey merged 11 commits from HooglyBoogly/blender:obj-normal-export-final into main 2024-01-26 18:55:10 +01:00
4 changed files with 60 additions and 90 deletions

View File

@ -360,7 +360,7 @@ void OBJWriter::write_poly_elements(FormatHandler &fh,
Span<int> poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i); Span<int> poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i);
Span<int> poly_uv_indices = obj_mesh_data.calc_poly_uv_indices(i); Span<int> poly_uv_indices = obj_mesh_data.calc_poly_uv_indices(i);
Vector<int> poly_normal_indices = obj_mesh_data.calc_poly_normal_indices(i); Span<int> poly_normal_indices = obj_mesh_data.calc_poly_normal_indices(i);
/* Write smoothing group if different from previous. */ /* Write smoothing group if different from previous. */
{ {

View File

@ -15,12 +15,14 @@
#include "BKE_mesh_mapping.hh" #include "BKE_mesh_mapping.hh"
#include "BKE_object.hh" #include "BKE_object.hh"
#include "BLI_array_utils.hh"
#include "BLI_listbase.h" #include "BLI_listbase.h"
#include "BLI_map.hh" #include "BLI_map.hh"
#include "BLI_math_matrix.h" #include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh" #include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h" #include "BLI_math_rotation.h"
#include "BLI_sort.hh" #include "BLI_sort.hh"
#include "BLI_vector_set.hh"
#include "DEG_depsgraph_query.hh" #include "DEG_depsgraph_query.hh"
@ -48,7 +50,6 @@ OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Obj
} }
if (export_mesh_) { if (export_mesh_) {
mesh_positions_ = export_mesh_->vert_positions();
mesh_edges_ = export_mesh_->edges(); mesh_edges_ = export_mesh_->edges();
mesh_faces_ = export_mesh_->faces(); mesh_faces_ = export_mesh_->faces();
mesh_corner_verts_ = export_mesh_->corner_verts(); mesh_corner_verts_ = export_mesh_->corner_verts();
@ -89,7 +90,6 @@ void OBJMesh::set_mesh(Mesh *mesh)
} }
owned_export_mesh_ = mesh; owned_export_mesh_ = mesh;
export_mesh_ = owned_export_mesh_; export_mesh_ = owned_export_mesh_;
mesh_positions_ = mesh->vert_positions();
mesh_edges_ = mesh->edges(); mesh_edges_ = mesh->edges();
mesh_faces_ = mesh->faces(); mesh_faces_ = mesh->faces();
mesh_corner_verts_ = mesh->corner_verts(); mesh_corner_verts_ = mesh->corner_verts();
@ -107,7 +107,7 @@ void OBJMesh::clear()
loop_to_uv_index_ = {}; loop_to_uv_index_ = {};
uv_coords_.clear_and_shrink(); uv_coords_.clear_and_shrink();
loop_to_normal_index_ = {}; loop_to_normal_index_ = {};
normal_coords_.clear_and_shrink(); normal_coords_ = {};
poly_order_ = {}; poly_order_ = {};
if (poly_smooth_groups_) { if (poly_smooth_groups_) {
MEM_freeN(poly_smooth_groups_); MEM_freeN(poly_smooth_groups_);
@ -195,11 +195,6 @@ int16_t OBJMesh::tot_materials() const
return this->materials.size(); return this->materials.size();
} }
int OBJMesh::tot_normal_indices() const
{
return tot_normal_indices_;
}
int OBJMesh::ith_smooth_group(const int face_index) const int OBJMesh::ith_smooth_group(const int face_index) const
{ {
/* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */ /* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */
@ -312,13 +307,6 @@ Span<int> OBJMesh::calc_poly_uv_indices(const int face_index) const
return loop_to_uv_index_.as_span().slice(mesh_faces_[face_index]); return loop_to_uv_index_.as_span().slice(mesh_faces_[face_index]);
} }
float3 OBJMesh::calc_poly_normal(const int face_index) const
{
const Span<int> face_verts = mesh_corner_verts_.slice(mesh_faces_[face_index]);
const float3 normal = bke::mesh::face_normal_calc(mesh_positions_, face_verts);
return math::normalize(world_and_axes_normal_transform_ * normal);
}
/** Round \a f to \a round_digits decimal digits. */ /** Round \a f to \a round_digits decimal digits. */
static float round_float_to_n_digits(const float f, int round_digits) static float round_float_to_n_digits(const float f, int round_digits)
{ {
@ -343,68 +331,59 @@ void OBJMesh::store_normal_coords_and_indices()
* Since normals are normalized, there will be no perceptible loss * Since normals are normalized, there will be no perceptible loss
* of precision when rounding to 4 digits. */ * of precision when rounding to 4 digits. */
constexpr int round_digits = 4; constexpr int round_digits = 4;
int cur_normal_index = 0; VectorSet<float3> unique_normals;
Map<float3, int> normal_to_index;
/* We don't know how many unique normals there will be, but this is a guess. */ /* We don't know how many unique normals there will be, but this is a guess. */
normal_to_index.reserve(export_mesh_->faces_num); unique_normals.reserve(export_mesh_->faces_num);
loop_to_normal_index_.reinitialize(export_mesh_->corners_num); loop_to_normal_index_.reinitialize(export_mesh_->corners_num);
loop_to_normal_index_.fill(-1);
Span<float3> corner_normals; /* Normals need inverse transpose of the regular matrix to handle non-uniform scale. */
if (ELEM(export_mesh_->normals_domain(), const float3x3 transform = world_and_axes_normal_transform_;
bke::MeshNormalDomain::Point, auto add_normal = [&](const float3 &normal) {
bke::MeshNormalDomain::Corner)) const float3 transformed = math::normalize(transform * normal);
{ const float3 rounded = round_float3_to_n_digits(transformed, round_digits);
corner_normals = export_mesh_->corner_normals(); return unique_normals.index_of_or_add(rounded);
} };
for (int face_index = 0; face_index < export_mesh_->faces_num; ++face_index) { switch (export_mesh_->normals_domain()) {
const IndexRange face = mesh_faces_[face_index]; case bke::MeshNormalDomain::Face: {
bool need_per_loop_normals = !corner_normals.is_empty() || !(sharp_faces_[face_index]); const Span<float3> face_normals = export_mesh_->face_normals();
if (need_per_loop_normals) { for (const int face : mesh_faces_.index_range()) {
for (const int corner : face) { const int index = add_normal(face_normals[face]);
BLI_assert(corner < export_mesh_->corners_num); loop_to_normal_index_.as_mutable_span().slice(mesh_faces_[face]).fill(index);
const float3 normal = math::normalize(world_and_axes_normal_transform_ * }
corner_normals[corner]); break;
const float3 rounded = round_float3_to_n_digits(normal, round_digits); }
int loop_norm_index = normal_to_index.lookup_default(rounded, -1); case bke::MeshNormalDomain::Point: {
if (loop_norm_index == -1) { const Span<float3> vert_normals = export_mesh_->vert_normals();
loop_norm_index = cur_normal_index++; Array<int> vert_normal_indices(vert_normals.size());
normal_to_index.add(rounded, loop_norm_index); const bke::LooseVertCache &verts_no_face = export_mesh_->verts_no_face();
normal_coords_.append(rounded); if (verts_no_face.count == 0) {
for (const int vert : vert_normals.index_range()) {
vert_normal_indices[vert] = add_normal(vert_normals[vert]);
} }
loop_to_normal_index_[corner] = loop_norm_index;
} }
else {
for (const int vert : vert_normals.index_range()) {
if (!verts_no_face.is_loose_bits[vert]) {
vert_normal_indices[vert] = add_normal(vert_normals[vert]);
}
}
}
array_utils::gather(vert_normal_indices.as_span(),
mesh_corner_verts_,
loop_to_normal_index_.as_mutable_span());
break;
} }
else { case bke::MeshNormalDomain::Corner: {
float3 poly_normal = calc_poly_normal(face_index); const Span<float3> corner_normals = export_mesh_->corner_normals();
float3 rounded_poly_normal = round_float3_to_n_digits(poly_normal, round_digits); for (const int corner : corner_normals.index_range()) {
int poly_norm_index = normal_to_index.lookup_default(rounded_poly_normal, -1); loop_to_normal_index_[corner] = add_normal(corner_normals[corner]);
if (poly_norm_index == -1) {
poly_norm_index = cur_normal_index++;
normal_to_index.add(rounded_poly_normal, poly_norm_index);
normal_coords_.append(rounded_poly_normal);
}
for (const int corner : face) {
BLI_assert(corner < export_mesh_->corners_num);
loop_to_normal_index_[corner] = poly_norm_index;
} }
break;
} }
} }
tot_normal_indices_ = cur_normal_index;
}
Vector<int> OBJMesh::calc_poly_normal_indices(const int face_index) const normal_coords_ = unique_normals.as_span();
{
if (loop_to_normal_index_.is_empty()) {
return {};
}
const IndexRange face = mesh_faces_[face_index];
Vector<int> r_poly_normal_indices(face.size());
for (const int i : IndexRange(face.size())) {
r_poly_normal_indices[i] = loop_to_normal_index_[face[i]];
}
return r_poly_normal_indices;
} }
int OBJMesh::tot_deform_groups() const int OBJMesh::tot_deform_groups() const

View File

@ -35,7 +35,6 @@ class OBJMesh : NonCopyable {
const Mesh *export_mesh_; const Mesh *export_mesh_;
/** A mesh owned here, if created or modified for the export. May be null. */ /** A mesh owned here, if created or modified for the export. May be null. */
Mesh *owned_export_mesh_ = nullptr; Mesh *owned_export_mesh_ = nullptr;
Span<float3> mesh_positions_;
Span<int2> mesh_edges_; Span<int2> mesh_edges_;
OffsetIndices<int> mesh_faces_; OffsetIndices<int> mesh_faces_;
Span<int> mesh_corner_verts_; Span<int> mesh_corner_verts_;
@ -58,19 +57,10 @@ class OBJMesh : NonCopyable {
*/ */
Vector<float2> uv_coords_; Vector<float2> uv_coords_;
/** /** Index into #normal_coords_ for every face corner. */
* Per-loop normal index.
*/
Array<int> loop_to_normal_index_; Array<int> loop_to_normal_index_;
/* /** De-duplicated normals, indexed by #loop_to_normal_index_. */
* Normal coords. Array<float3> normal_coords_;
*/
Vector<float3> normal_coords_;
/*
* Total number of normal indices (maximum entry, plus 1, in
* the loop_to_norm_index_ vector).
*/
int tot_normal_indices_ = 0;
/** /**
* Total smooth groups in an object. * Total smooth groups in an object.
*/ */
@ -100,7 +90,6 @@ class OBJMesh : NonCopyable {
int tot_vertices() const; int tot_vertices() const;
int tot_faces() const; int tot_faces() const;
int tot_uv_vertices() const; int tot_uv_vertices() const;
int tot_normal_indices() const;
int tot_edges() const; int tot_edges() const;
int tot_deform_groups() const; int tot_deform_groups() const;
bool is_mirrored_transform() const bool is_mirrored_transform() const
@ -153,19 +142,13 @@ class OBJMesh : NonCopyable {
return uv_coords_; return uv_coords_;
} }
Span<int> calc_poly_uv_indices(int face_index) const; Span<int> calc_poly_uv_indices(int face_index) const;
/**
* Calculate polygon normal of a polygon at given index.
*
* Should be used for flat-shaded polygons.
*/
float3 calc_poly_normal(int face_index) const;
/** /**
* Find the unique normals of the mesh and stores them in a member variable. * Find the unique normals of the mesh and stores them in a member variable.
* Also stores the indices into that vector with for each loop. * Also stores the indices into that vector with for each loop.
*/ */
void store_normal_coords_and_indices(); void store_normal_coords_and_indices();
/* Get normals calculate by store_normal_coords_and_indices. */ /* Get normals calculate by store_normal_coords_and_indices. */
const Vector<float3> &get_normal_coords() const Span<float3> get_normal_coords() const
{ {
return normal_coords_; return normal_coords_;
} }
@ -174,7 +157,15 @@ class OBJMesh : NonCopyable {
* \param face_index: Index of the polygon to calculate indices for. * \param face_index: Index of the polygon to calculate indices for.
* \return Vector of normal indices, aligned with vertices of polygon. * \return Vector of normal indices, aligned with vertices of polygon.
*/ */
Vector<int> calc_poly_normal_indices(int face_index) const; Span<int> calc_poly_normal_indices(const int face_index) const
{
if (loop_to_normal_index_.is_empty()) {
return {};
}
const IndexRange face = mesh_faces_[face_index];
return loop_to_normal_index_.as_span().slice(face);
}
/** /**
* Find the most representative vertex group of a polygon. * Find the most representative vertex group of a polygon.
* *

View File

@ -185,7 +185,7 @@ static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_a
index_offsets.append(offsets); index_offsets.append(offsets);
offsets.vertex_offset += obj.tot_vertices(); offsets.vertex_offset += obj.tot_vertices();
offsets.uv_vertex_offset += obj.tot_uv_vertices(); offsets.uv_vertex_offset += obj.tot_uv_vertices();
offsets.normal_offset += obj.tot_normal_indices(); offsets.normal_offset += obj.get_normal_coords().size();
} }
/* Parallel over meshes: main result writing. */ /* Parallel over meshes: main result writing. */