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_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. */
{

View File

@ -15,12 +15,14 @@
#include "BKE_mesh_mapping.hh"
#include "BKE_object.hh"
#include "BLI_array_utils.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_sort.hh"
#include "BLI_vector_set.hh"
#include "DEG_depsgraph_query.hh"
@ -48,7 +50,6 @@ OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Obj
}
if (export_mesh_) {
mesh_positions_ = export_mesh_->vert_positions();
mesh_edges_ = export_mesh_->edges();
mesh_faces_ = export_mesh_->faces();
mesh_corner_verts_ = export_mesh_->corner_verts();
@ -89,7 +90,6 @@ void OBJMesh::set_mesh(Mesh *mesh)
}
owned_export_mesh_ = mesh;
export_mesh_ = owned_export_mesh_;
mesh_positions_ = mesh->vert_positions();
mesh_edges_ = mesh->edges();
mesh_faces_ = mesh->faces();
mesh_corner_verts_ = mesh->corner_verts();
@ -107,7 +107,7 @@ void OBJMesh::clear()
loop_to_uv_index_ = {};
uv_coords_.clear_and_shrink();
loop_to_normal_index_ = {};
normal_coords_.clear_and_shrink();
normal_coords_ = {};
poly_order_ = {};
if (poly_smooth_groups_) {
MEM_freeN(poly_smooth_groups_);
@ -195,11 +195,6 @@ int16_t OBJMesh::tot_materials() const
return this->materials.size();
}
int OBJMesh::tot_normal_indices() const
{
return tot_normal_indices_;
}
int OBJMesh::ith_smooth_group(const int face_index) const
{
/* 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]);
}
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. */
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
* of precision when rounding to 4 digits. */
constexpr int round_digits = 4;
int cur_normal_index = 0;
Map<float3, int> normal_to_index;
VectorSet<float3> unique_normals;
/* 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_.fill(-1);
Span<float3> corner_normals;
if (ELEM(export_mesh_->normals_domain(),
bke::MeshNormalDomain::Point,
bke::MeshNormalDomain::Corner))
{
corner_normals = export_mesh_->corner_normals();
}
/* Normals need inverse transpose of the regular matrix to handle non-uniform scale. */
const float3x3 transform = world_and_axes_normal_transform_;
auto add_normal = [&](const float3 &normal) {
const float3 transformed = math::normalize(transform * normal);
const float3 rounded = round_float3_to_n_digits(transformed, round_digits);
return unique_normals.index_of_or_add(rounded);
};
for (int face_index = 0; face_index < export_mesh_->faces_num; ++face_index) {
const IndexRange face = mesh_faces_[face_index];
bool need_per_loop_normals = !corner_normals.is_empty() || !(sharp_faces_[face_index]);
if (need_per_loop_normals) {
for (const int corner : face) {
BLI_assert(corner < export_mesh_->corners_num);
const float3 normal = math::normalize(world_and_axes_normal_transform_ *
corner_normals[corner]);
const float3 rounded = round_float3_to_n_digits(normal, round_digits);
int loop_norm_index = normal_to_index.lookup_default(rounded, -1);
if (loop_norm_index == -1) {
loop_norm_index = cur_normal_index++;
normal_to_index.add(rounded, loop_norm_index);
normal_coords_.append(rounded);
switch (export_mesh_->normals_domain()) {
case bke::MeshNormalDomain::Face: {
const Span<float3> face_normals = export_mesh_->face_normals();
for (const int face : mesh_faces_.index_range()) {
const int index = add_normal(face_normals[face]);
loop_to_normal_index_.as_mutable_span().slice(mesh_faces_[face]).fill(index);
}
break;
}
case bke::MeshNormalDomain::Point: {
const Span<float3> vert_normals = export_mesh_->vert_normals();
Array<int> vert_normal_indices(vert_normals.size());
const bke::LooseVertCache &verts_no_face = export_mesh_->verts_no_face();
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 {
float3 poly_normal = calc_poly_normal(face_index);
float3 rounded_poly_normal = round_float3_to_n_digits(poly_normal, round_digits);
int poly_norm_index = normal_to_index.lookup_default(rounded_poly_normal, -1);
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;
case bke::MeshNormalDomain::Corner: {
const Span<float3> corner_normals = export_mesh_->corner_normals();
for (const int corner : corner_normals.index_range()) {
loop_to_normal_index_[corner] = add_normal(corner_normals[corner]);
}
break;
}
}
tot_normal_indices_ = cur_normal_index;
}
Vector<int> OBJMesh::calc_poly_normal_indices(const int face_index) const
{
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;
normal_coords_ = unique_normals.as_span();
}
int OBJMesh::tot_deform_groups() const

View File

@ -35,7 +35,6 @@ class OBJMesh : NonCopyable {
const Mesh *export_mesh_;
/** A mesh owned here, if created or modified for the export. May be null. */
Mesh *owned_export_mesh_ = nullptr;
Span<float3> mesh_positions_;
Span<int2> mesh_edges_;
OffsetIndices<int> mesh_faces_;
Span<int> mesh_corner_verts_;
@ -58,19 +57,10 @@ class OBJMesh : NonCopyable {
*/
Vector<float2> uv_coords_;
/**
* Per-loop normal index.
*/
/** Index into #normal_coords_ for every face corner. */
Array<int> loop_to_normal_index_;
/*
* 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;
/** De-duplicated normals, indexed by #loop_to_normal_index_. */
Array<float3> normal_coords_;
/**
* Total smooth groups in an object.
*/
@ -100,7 +90,6 @@ class OBJMesh : NonCopyable {
int tot_vertices() const;
int tot_faces() const;
int tot_uv_vertices() const;
int tot_normal_indices() const;
int tot_edges() const;
int tot_deform_groups() const;
bool is_mirrored_transform() const
@ -153,19 +142,13 @@ class OBJMesh : NonCopyable {
return uv_coords_;
}
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.
* Also stores the indices into that vector with for each loop.
*/
void 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_;
}
@ -174,7 +157,15 @@ class OBJMesh : NonCopyable {
* \param face_index: Index of the polygon to calculate indices for.
* \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.
*

View File

@ -185,7 +185,7 @@ static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_a
index_offsets.append(offsets);
offsets.vertex_offset += obj.tot_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. */