Blender V5.0
obj_export_mesh.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_attribute.hh"
10#include "BKE_customdata.hh"
11#include "BKE_deform.hh"
12#include "BKE_lib_id.hh"
13#include "BKE_material.hh"
14#include "BKE_mesh.hh"
15#include "BKE_mesh_mapping.hh"
16#include "BKE_object.hh"
17
18#include "BLI_array_utils.hh"
19#include "BLI_listbase.h"
20#include "BLI_map.hh"
21#include "BLI_math_matrix.hh"
22#include "BLI_math_rotation.h"
23#include "BLI_sort.hh"
24#include "BLI_vector_set.hh"
25
27
28#include "DNA_meshdata_types.h"
29#include "DNA_modifier_types.h"
30#include "DNA_object_types.h"
31
32#include "obj_export_mesh.hh"
33
34#include "bmesh.hh"
35#include "bmesh_tools.hh"
36
37namespace blender::io::obj {
38OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object)
39{
40 /* We need to copy the object because it may be in temporary space. */
41 Object *obj_eval = DEG_get_evaluated(depsgraph, mesh_object);
42 object_name_ = obj_eval->id.name + 2;
43 export_mesh_ = nullptr;
44
45 if (obj_eval->type == OB_MESH) {
46 export_mesh_ = export_params.apply_modifiers ? BKE_object_get_evaluated_mesh(obj_eval) :
48 }
49
50 if (export_mesh_) {
51 mesh_edges_ = export_mesh_->edges();
52 mesh_faces_ = export_mesh_->faces();
53 mesh_corner_verts_ = export_mesh_->corner_verts();
54 sharp_faces_ = *export_mesh_->attributes().lookup_or_default<bool>(
55 "sharp_face", bke::AttrDomain::Face, false);
56 }
57 else {
58 /* Curves and NURBS surfaces need a new mesh when they're
59 * exported in the form of vertices and edges.
60 */
61 this->set_mesh(BKE_mesh_new_from_object(depsgraph, obj_eval, true, true, true));
62 }
63 if (export_params.export_triangulated_mesh && obj_eval->type == OB_MESH) {
64 this->triangulate_mesh_eval();
65 }
66
67 this->materials.reinitialize(export_mesh_->totcol);
68 for (const int i : this->materials.index_range()) {
69 this->materials[i] = BKE_object_material_get_eval(obj_eval, i + 1);
70 }
71
72 set_world_axes_transform(*obj_eval,
73 export_params.forward_axis,
74 export_params.up_axis,
75 export_params.global_scale,
76 export_params.apply_transform);
77}
78
83{
84 clear();
85}
86
87void OBJMesh::set_mesh(Mesh *mesh)
88{
89 if (owned_export_mesh_) {
90 BKE_id_free(nullptr, owned_export_mesh_);
91 }
92 owned_export_mesh_ = mesh;
93 export_mesh_ = owned_export_mesh_;
94 mesh_edges_ = mesh->edges();
95 mesh_faces_ = mesh->faces();
96 mesh_corner_verts_ = mesh->corner_verts();
97 sharp_faces_ = *export_mesh_->attributes().lookup_or_default<bool>(
98 "sharp_face", bke::AttrDomain::Face, false);
99}
100
102{
103 if (owned_export_mesh_) {
104 BKE_id_free(nullptr, owned_export_mesh_);
105 owned_export_mesh_ = nullptr;
106 }
107 export_mesh_ = nullptr;
108 corner_to_uv_index_ = {};
109 uv_coords_.clear_and_shrink();
110 corner_to_normal_index_ = {};
111 normal_coords_ = {};
112 face_order_ = {};
113 if (face_smooth_groups_) {
114 MEM_freeN(face_smooth_groups_);
115 face_smooth_groups_ = nullptr;
116 }
117}
118
119void OBJMesh::triangulate_mesh_eval()
120{
121 if (export_mesh_->faces_num <= 0) {
122 return;
123 }
124 const BMeshCreateParams bm_create_params = {false};
125 BMeshFromMeshParams bm_convert_params{};
126 bm_convert_params.calc_face_normal = true;
127 bm_convert_params.calc_vert_normal = true;
128 bm_convert_params.add_key_index = false;
129 bm_convert_params.use_shapekey = false;
130
131 /* Lower threshold where triangulation of a face starts, i.e. a quadrilateral will be
132 * triangulated here. */
133 const int triangulate_min_verts = 4;
134
135 BMesh *bmesh = BKE_mesh_to_bmesh_ex(export_mesh_, &bm_create_params, &bm_convert_params);
139 triangulate_min_verts,
140 false,
141 nullptr,
142 nullptr,
143 nullptr);
144 Mesh *triangulated = BKE_mesh_from_bmesh_for_eval_nomain(bmesh, nullptr, export_mesh_);
145 BM_mesh_free(bmesh);
146 this->set_mesh(triangulated);
147}
148
149void OBJMesh::set_world_axes_transform(const Object &obj_eval,
150 const eIOAxis forward,
151 const eIOAxis up,
152 const float global_scale,
153 const bool apply_transform)
154{
155 float3x3 axes_transform;
156 /* +Y-forward and +Z-up are the default Blender axis settings. */
157 mat3_from_axis_conversion(forward, up, IO_AXIS_Y, IO_AXIS_Z, axes_transform.ptr());
158
159 const float4x4 &object_to_world = apply_transform ? obj_eval.object_to_world() :
161 const float3x3 transform = axes_transform * float3x3(object_to_world);
162
163 world_and_axes_transform_ = float4x4(transform);
164 world_and_axes_transform_.location() = axes_transform * object_to_world.location();
165 world_and_axes_transform_[3][3] = object_to_world[3][3];
166
167 world_and_axes_transform_ = math::from_scale<float4x4>(float3(global_scale)) *
168 world_and_axes_transform_;
169
170 /* Normals need inverse transpose of the regular matrix to handle non-uniform scale. */
171 world_and_axes_normal_transform_ = math::transpose(math::invert(transform));
172
173 mirrored_transform_ = math::is_negative(world_and_axes_normal_transform_);
174}
175
177{
178 return export_mesh_->verts_num;
179}
180
182{
183 return export_mesh_->faces_num;
184}
185
187{
188 return int(uv_coords_.size());
189}
190
192{
193 return export_mesh_->edges_num;
194}
195
197{
198 return this->materials.size();
199}
200
201int OBJMesh::ith_smooth_group(const int face_index) const
202{
203 /* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */
204 BLI_assert(tot_smooth_groups_ != -NEGATIVE_INIT);
205 BLI_assert(face_smooth_groups_);
206 return face_smooth_groups_[face_index];
207}
208
209void OBJMesh::calc_smooth_groups(const bool use_bitflags)
210{
211 const bke::AttributeAccessor attributes = export_mesh_->attributes();
212 const VArraySpan sharp_edges = *attributes.lookup<bool>("sharp_edge", bke::AttrDomain::Edge);
213 const VArraySpan sharp_faces = *attributes.lookup<bool>("sharp_face", bke::AttrDomain::Face);
214 if (use_bitflags) {
215 face_smooth_groups_ = BKE_mesh_calc_smoothgroups_bitflags(mesh_edges_.size(),
216 export_mesh_->verts_num,
217 mesh_faces_,
218 export_mesh_->corner_edges(),
219 export_mesh_->corner_verts(),
220 sharp_edges,
221 sharp_faces,
222 true,
223 &tot_smooth_groups_);
224 }
225 else {
226 face_smooth_groups_ = BKE_mesh_calc_smoothgroups(mesh_edges_.size(),
227 mesh_faces_,
228 export_mesh_->corner_edges(),
229 sharp_edges,
230 sharp_faces,
231 &tot_smooth_groups_);
232 }
233}
234
236{
237 const bke::AttributeAccessor attributes = export_mesh_->attributes();
238 const VArray<int> material_indices = *attributes.lookup_or_default<int>(
239 "material_index", bke::AttrDomain::Face, 0);
240 if (material_indices.is_single() && material_indices.get_internal_single() == 0) {
241 return;
242 }
243 const VArraySpan<int> material_indices_span(material_indices);
244
245 /* Sort faces by their material index. */
246 face_order_.reinitialize(material_indices_span.size());
247 array_utils::fill_index_range(face_order_.as_mutable_span());
248 blender::parallel_sort(face_order_.begin(), face_order_.end(), [&](int a, int b) {
249 int mat_a = material_indices_span[a];
250 int mat_b = material_indices_span[b];
251 if (mat_a != mat_b) {
252 return mat_a < mat_b;
253 }
254 return a < b;
255 });
256}
257
258bool OBJMesh::is_ith_face_smooth(const int face_index) const
259{
260 return !sharp_faces_[face_index];
261}
262
264{
265 return object_name_;
266}
267
269{
270 return export_mesh_->id.name + 2;
271}
272
274{
275 const StringRef active_uv_name = CustomData_get_active_layer_name(&export_mesh_->corner_data,
277 if (active_uv_name.is_empty()) {
278 uv_coords_.clear();
279 return;
280 }
281 const bke::AttributeAccessor attributes = export_mesh_->attributes();
282 const VArraySpan uv_map = *attributes.lookup<float2>(active_uv_name, bke::AttrDomain::Corner);
283 if (uv_map.is_empty()) {
284 uv_coords_.clear();
285 return;
286 }
287
288 Map<float2, int> uv_to_index;
289
290 /* We don't know how many unique UVs there will be, but this is a guess. */
291 uv_to_index.reserve(export_mesh_->verts_num);
292 uv_coords_.reserve(export_mesh_->verts_num);
293
294 corner_to_uv_index_.reinitialize(uv_map.size());
295
296 for (int index = 0; index < int(uv_map.size()); index++) {
297 float2 uv = uv_map[index];
298 int uv_index = uv_to_index.lookup_default(uv, -1);
299 if (uv_index == -1) {
300 uv_index = uv_to_index.size();
301 uv_to_index.add(uv, uv_index);
302 uv_coords_.append(uv);
303 }
304 corner_to_uv_index_[index] = uv_index;
305 }
306}
307
309static float round_float_to_n_digits(const float f, int round_digits)
310{
311 float scale = powf(10.0, round_digits);
312 return ceilf(scale * f - 0.49999999f) / scale;
313}
314
315static float3 round_float3_to_n_digits(const float3 &v, int round_digits)
316{
317 float3 ans;
318 ans.x = round_float_to_n_digits(v.x, round_digits);
319 ans.y = round_float_to_n_digits(v.y, round_digits);
320 ans.z = round_float_to_n_digits(v.z, round_digits);
321 return ans;
322}
323
325{
326 /* We'll round normal components to 4 digits.
327 * This will cover up some minor differences
328 * between floating point calculations on different platforms.
329 * Since normals are normalized, there will be no perceptible loss
330 * of precision when rounding to 4 digits. */
331 constexpr int round_digits = 4;
332 VectorSet<float3> unique_normals;
333 /* We don't know how many unique normals there will be, but this is a guess. */
334 unique_normals.reserve(export_mesh_->faces_num);
335 corner_to_normal_index_.reinitialize(export_mesh_->corners_num);
336
337 /* Normals need inverse transpose of the regular matrix to handle non-uniform scale. */
338 const float3x3 transform = world_and_axes_normal_transform_;
339 auto add_normal = [&](const float3 &normal) {
340 const float3 transformed = math::normalize(transform * normal);
341 const float3 rounded = round_float3_to_n_digits(transformed, round_digits);
342 return unique_normals.index_of_or_add(rounded);
343 };
344
345 switch (export_mesh_->normals_domain()) {
347 const Span<float3> face_normals = export_mesh_->face_normals();
348 for (const int face : mesh_faces_.index_range()) {
349 const int index = add_normal(face_normals[face]);
350 corner_to_normal_index_.as_mutable_span().slice(mesh_faces_[face]).fill(index);
351 }
352 break;
353 }
355 const Span<float3> vert_normals = export_mesh_->vert_normals();
356 Array<int> vert_normal_indices(vert_normals.size());
357 const bke::LooseVertCache &verts_no_face = export_mesh_->verts_no_face();
358 if (verts_no_face.count == 0) {
359 for (const int vert : vert_normals.index_range()) {
360 vert_normal_indices[vert] = add_normal(vert_normals[vert]);
361 }
362 }
363 else {
364 for (const int vert : vert_normals.index_range()) {
365 if (!verts_no_face.is_loose_bits[vert]) {
366 vert_normal_indices[vert] = add_normal(vert_normals[vert]);
367 }
368 }
369 }
370 array_utils::gather(vert_normal_indices.as_span(),
371 mesh_corner_verts_,
372 corner_to_normal_index_.as_mutable_span());
373 break;
374 }
376 const Span<float3> corner_normals = export_mesh_->corner_normals();
377 for (const int corner : corner_normals.index_range()) {
378 corner_to_normal_index_[corner] = add_normal(corner_normals[corner]);
379 }
380 break;
381 }
382 }
383
384 normal_coords_ = unique_normals.as_span();
385}
386
388{
389 return BLI_listbase_count(&export_mesh_->vertex_group_names);
390}
391
392int16_t OBJMesh::get_face_deform_group_index(const int face_index,
393 MutableSpan<float> group_weights) const
394{
395 BLI_assert(face_index < export_mesh_->faces_num);
396 BLI_assert(group_weights.size() == BLI_listbase_count(&export_mesh_->vertex_group_names));
397 const Span<MDeformVert> dverts = export_mesh_->deform_verts();
398 if (dverts.is_empty()) {
399 return NOT_FOUND;
400 }
401
402 group_weights.fill(0);
403 bool found_any_group = false;
404 for (const int vert : mesh_corner_verts_.slice(mesh_faces_[face_index])) {
405 const MDeformVert &dv = dverts[vert];
406 for (int weight_i = 0; weight_i < dv.totweight; ++weight_i) {
407 const auto group = dv.dw[weight_i].def_nr;
408 if (group < group_weights.size()) {
409 group_weights[group] += dv.dw[weight_i].weight;
410 found_any_group = true;
411 }
412 }
413 }
414
415 if (!found_any_group) {
416 return NOT_FOUND;
417 }
418 /* Index of the group with maximum vertices. */
419 int16_t max_idx = std::max_element(group_weights.begin(), group_weights.end()) -
420 group_weights.begin();
421 return max_idx;
422}
423
424const char *OBJMesh::get_face_deform_group_name(const int16_t def_group_index) const
425{
426 const bDeformGroup &vertex_group = *(static_cast<bDeformGroup *>(
427 BLI_findlink(&export_mesh_->vertex_group_names, def_group_index)));
428 return vertex_group.name;
429}
430
431} // namespace blender::io::obj
CustomData interface, see also DNA_customdata_types.h.
const char * CustomData_get_active_layer_name(const CustomData *data, eCustomDataType type)
support for deformation groups and hooks.
void BKE_id_free(Main *bmain, void *idv)
General operations, lookup, etc. for materials.
Material * BKE_object_material_get_eval(Object *ob, short act)
Mesh * BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm, const CustomData_MeshMasks *cd_mask_extra, const Mesh *me_settings)
Mesh * BKE_mesh_new_from_object(Depsgraph *depsgraph, Object *object, bool preserve_all_data_layers, bool preserve_origindex, bool ensure_subdivision)
BMesh * BKE_mesh_to_bmesh_ex(const Mesh *mesh, const BMeshCreateParams *create_params, const BMeshFromMeshParams *convert_params)
int * BKE_mesh_calc_smoothgroups(int edges_num, blender::OffsetIndices< int > faces, blender::Span< int > corner_edges, blender::Span< bool > sharp_edges, blender::Span< bool > sharp_faces, int *r_totgroup)
int * BKE_mesh_calc_smoothgroups_bitflags(int edges_num, int verts_num, blender::OffsetIndices< int > faces, blender::Span< int > corner_edges, blender::Span< int > corner_verts, blender::Span< bool > sharp_edges, blender::Span< bool > sharp_faces, bool use_boundary_vertices_for_bitflags, int *r_totgroup)
General operations, lookup, etc. for blender objects.
const Mesh * BKE_object_get_pre_modified_mesh(const Object *object)
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
#define BLI_assert(a)
Definition BLI_assert.h:46
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
bool mat3_from_axis_conversion(int src_forward, int src_up, int dst_forward, int dst_up, float r_mat[3][3])
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
@ CD_PROP_FLOAT2
@ MOD_TRIANGULATE_QUAD_SHORTEDGE
@ MOD_TRIANGULATE_NGON_BEAUTY
Object is a sort of wrapper for general info.
@ OB_MESH
struct Object Object
eIOAxis
@ IO_AXIS_Y
@ IO_AXIS_Z
void BM_mesh_free(BMesh *bm)
BMesh Free Mesh.
ATTR_WARN_UNUSED_RESULT const BMVert * v
void BM_mesh_triangulate(BMesh *bm, const int quad_method, const int ngon_method, const int min_vertices, const bool tag_only, BMOperator *op, BMOpSlot *slot_facemap_out, BMOpSlot *slot_facemap_double_out)
BPy_StructRNA * depsgraph
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
AttributeSet attributes
Span< T > as_span() const
Definition BLI_array.hh:243
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
int64_t size() const
Definition BLI_map.hh:976
void reserve(int64_t n)
Definition BLI_map.hh:1028
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr T * end() const
Definition BLI_span.hh:548
constexpr T * begin() const
Definition BLI_span.hh:544
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
constexpr bool is_empty() const
int64_t index_of_or_add(const Key &key)
void reserve(const int64_t n)
Span< Key > as_span() const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, AttrType data_type, const void *default_value=nullptr) const
GAttributeReader lookup(const StringRef attribute_id) const
const char * get_face_deform_group_name(int16_t def_group_index) const
int16_t get_face_deform_group_index(int face_index, MutableSpan< float > group_weights) const
bool is_ith_face_smooth(int face_index) const
void calc_smooth_groups(bool use_bitflags)
StringRef get_object_mesh_name() const
Array< const Material * > materials
StringRef get_object_name() const
OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object)
int ith_smooth_group(int face_index) const
#define powf(x, y)
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
void gather(const GVArray &src, const IndexMask &indices, GMutableSpan dst, int64_t grain_size=4096)
void fill_index_range(MutableSpan< T > span, const T start=0)
static float round_float_to_n_digits(const float f, int round_digits)
static float3 round_float3_to_n_digits(const float3 &v, int round_digits)
MatBase< T, NumCol, NumRow > transpose(const MatBase< T, NumRow, NumCol > &mat)
bool is_negative(const MatBase< T, 3, 3 > &mat)
CartesianBasis invert(const CartesianBasis &basis)
MatT from_scale(const VecBase< typename MatT::base_type, ScaleDim > &scale)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
MatBase< float, 4, 4 > float4x4
void parallel_sort(RandomAccessIterator begin, RandomAccessIterator end)
Definition BLI_sort.hh:23
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
#define ceilf
char name[258]
Definition DNA_ID.h:432
struct MDeformWeight * dw
unsigned int def_nr
int faces_num
blender::BitVector is_loose_bits
i
Definition text_draw.cc:230