1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2019 Blender Foundation.
17 * All rights reserved.
18 */
19 #include "usd_writer_mesh.h"
20 #include "usd_hierarchy_iterator.h"
21
22 #include <pxr/usd/usdGeom/mesh.h>
23 #include <pxr/usd/usdShade/material.h>
24 #include <pxr/usd/usdShade/materialBindingAPI.h>
25
26 #include "BLI_assert.h"
27 #include "BLI_math_vector.h"
28
29 #include "BKE_customdata.h"
30 #include "BKE_lib_id.h"
31 #include "BKE_material.h"
32 #include "BKE_mesh.h"
33 #include "BKE_modifier.h"
34 #include "BKE_object.h"
35
36 #include "DEG_depsgraph.h"
37
38 #include "DNA_layer_types.h"
39 #include "DNA_mesh_types.h"
40 #include "DNA_meshdata_types.h"
41 #include "DNA_modifier_types.h"
42 #include "DNA_object_fluidsim_types.h"
43 #include "DNA_particle_types.h"
44
45 #include <iostream>
46
47 namespace blender::io::usd {
48
USDGenericMeshWriter(const USDExporterContext & ctx)49 USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
50 {
51 }
52
is_supported(const HierarchyContext * context) const53 bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
54 {
55 if (usd_export_context_.export_params.visible_objects_only) {
56 return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
57 }
58 return true;
59 }
60
do_write(HierarchyContext & context)61 void USDGenericMeshWriter::do_write(HierarchyContext &context)
62 {
63 Object *object_eval = context.object;
64 bool needsfree = false;
65 Mesh *mesh = get_export_mesh(object_eval, needsfree);
66
67 if (mesh == nullptr) {
68 return;
69 }
70
71 try {
72 write_mesh(context, mesh);
73
74 if (needsfree) {
75 free_export_mesh(mesh);
76 }
77 }
78 catch (...) {
79 if (needsfree) {
80 free_export_mesh(mesh);
81 }
82 throw;
83 }
84 }
85
free_export_mesh(Mesh * mesh)86 void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
87 {
88 BKE_id_free(nullptr, mesh);
89 }
90
91 struct USDMeshData {
92 pxr::VtArray<pxr::GfVec3f> points;
93 pxr::VtIntArray face_vertex_counts;
94 pxr::VtIntArray face_indices;
95 std::map<short, pxr::VtIntArray> face_groups;
96
97 /* The length of this array specifies the number of creases on the surface. Each element gives
98 * the number of (must be adjacent) vertices in each crease, whose indices are linearly laid out
99 * in the 'creaseIndices' attribute. Since each crease must be at least one edge long, each
100 * element of this array should be greater than one. */
101 pxr::VtIntArray crease_lengths;
102 /* The indices of all vertices forming creased edges. The size of this array must be equal to the
103 * sum of all elements of the 'creaseLengths' attribute. */
104 pxr::VtIntArray crease_vertex_indices;
105 /* The per-crease or per-edge sharpness for all creases (Usd.Mesh.SHARPNESS_INFINITE for a
106 * perfectly sharp crease). Since 'creaseLengths' encodes the number of vertices in each crease,
107 * the number of elements in this array will be either 'len(creaseLengths)' or the sum over all X
108 * of '(creaseLengths[X] - 1)'. Note that while the RI spec allows each crease to have either a
109 * single sharpness or a value per-edge, USD will encode either a single sharpness per crease on
110 * a mesh, or sharpness's for all edges making up the creases on a mesh. */
111 pxr::VtFloatArray crease_sharpnesses;
112 };
113
write_uv_maps(const Mesh * mesh,pxr::UsdGeomMesh usd_mesh)114 void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
115 {
116 pxr::UsdTimeCode timecode = get_export_time_code();
117
118 const CustomData *ldata = &mesh->ldata;
119 for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
120 const CustomDataLayer *layer = &ldata->layers[layer_idx];
121 if (layer->type != CD_MLOOPUV) {
122 continue;
123 }
124
125 /* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
126 * The primvar name is the same as the UV Map name. This is to allow the standard name "st"
127 * for texture coordinates by naming the UV Map as such, without having to guess which UV Map
128 * is the "standard" one. */
129 pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
130 pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
131 primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
132
133 MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data);
134 pxr::VtArray<pxr::GfVec2f> uv_coords;
135 for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
136 uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
137 }
138
139 if (!uv_coords_primvar.HasValue()) {
140 uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
141 }
142 const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
143 usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
144 }
145 }
146
write_mesh(HierarchyContext & context,Mesh * mesh)147 void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
148 {
149 pxr::UsdTimeCode timecode = get_export_time_code();
150 pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
151 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
152 const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
153
154 pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path);
155 write_visibility(context, timecode, usd_mesh);
156
157 USDMeshData usd_mesh_data;
158 get_geometry_data(mesh, usd_mesh_data);
159
160 if (usd_export_context_.export_params.use_instancing && context.is_instance()) {
161 if (!mark_as_instance(context, usd_mesh.GetPrim())) {
162 return;
163 }
164
165 /* The material path will be of the form </_materials/{material name}>, which is outside the
166 * sub-tree pointed to by ref_path. As a result, the referenced data is not allowed to point
167 * out of its own sub-tree. It does work when we override the material with exactly the same
168 * path, though.*/
169 if (usd_export_context_.export_params.export_materials) {
170 assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
171 }
172
173 return;
174 }
175
176 pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
177 pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
178 true);
179 pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
180 true);
181
182 if (!attr_points.HasValue()) {
183 /* Provide the initial value as default. This makes USD write the value as constant if they
184 * don't change over time. */
185 attr_points.Set(usd_mesh_data.points, defaultTime);
186 attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime);
187 attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime);
188 }
189
190 usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
191 usd_value_writer_.SetAttribute(
192 attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
193 usd_value_writer_.SetAttribute(
194 attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
195
196 if (!usd_mesh_data.crease_lengths.empty()) {
197 pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true);
198 pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true);
199 pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(),
200 true);
201
202 if (!attr_crease_lengths.HasValue()) {
203 attr_crease_lengths.Set(usd_mesh_data.crease_lengths, defaultTime);
204 attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, defaultTime);
205 attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, defaultTime);
206 }
207
208 usd_value_writer_.SetAttribute(
209 attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
210 usd_value_writer_.SetAttribute(
211 attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
212 usd_value_writer_.SetAttribute(
213 attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
214 }
215
216 if (usd_export_context_.export_params.export_uvmaps) {
217 write_uv_maps(mesh, usd_mesh);
218 }
219 if (usd_export_context_.export_params.export_normals) {
220 write_normals(mesh, usd_mesh);
221 }
222 write_surface_velocity(context.object, mesh, usd_mesh);
223
224 /* TODO(Sybren): figure out what happens when the face groups change. */
225 if (frame_has_been_written_) {
226 return;
227 }
228
229 usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none);
230
231 if (usd_export_context_.export_params.export_materials) {
232 assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
233 }
234 }
235
get_vertices(const Mesh * mesh,USDMeshData & usd_mesh_data)236 static void get_vertices(const Mesh *mesh, USDMeshData &usd_mesh_data)
237 {
238 usd_mesh_data.points.reserve(mesh->totvert);
239
240 const MVert *verts = mesh->mvert;
241 for (int i = 0; i < mesh->totvert; ++i) {
242 usd_mesh_data.points.push_back(pxr::GfVec3f(verts[i].co));
243 }
244 }
245
get_loops_polys(const Mesh * mesh,USDMeshData & usd_mesh_data)246 static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
247 {
248 /* Only construct face groups (a.k.a. geometry subsets) when we need them for material
249 * assignments. */
250 bool construct_face_groups = mesh->totcol > 1;
251
252 usd_mesh_data.face_vertex_counts.reserve(mesh->totpoly);
253 usd_mesh_data.face_indices.reserve(mesh->totloop);
254
255 MLoop *mloop = mesh->mloop;
256 MPoly *mpoly = mesh->mpoly;
257 for (int i = 0; i < mesh->totpoly; ++i, ++mpoly) {
258 MLoop *loop = mloop + mpoly->loopstart;
259 usd_mesh_data.face_vertex_counts.push_back(mpoly->totloop);
260 for (int j = 0; j < mpoly->totloop; ++j, ++loop) {
261 usd_mesh_data.face_indices.push_back(loop->v);
262 }
263
264 if (construct_face_groups) {
265 usd_mesh_data.face_groups[mpoly->mat_nr].push_back(i);
266 }
267 }
268 }
269
get_creases(const Mesh * mesh,USDMeshData & usd_mesh_data)270 static void get_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
271 {
272 const float factor = 1.0f / 255.0f;
273
274 MEdge *edge = mesh->medge;
275 float sharpness;
276 for (int edge_idx = 0, totedge = mesh->totedge; edge_idx < totedge; ++edge_idx, ++edge) {
277 if (edge->crease == 0) {
278 continue;
279 }
280
281 if (edge->crease == 255) {
282 sharpness = pxr::UsdGeomMesh::SHARPNESS_INFINITE;
283 }
284 else {
285 sharpness = static_cast<float>(edge->crease) * factor;
286 }
287
288 usd_mesh_data.crease_vertex_indices.push_back(edge->v1);
289 usd_mesh_data.crease_vertex_indices.push_back(edge->v2);
290 usd_mesh_data.crease_lengths.push_back(2);
291 usd_mesh_data.crease_sharpnesses.push_back(sharpness);
292 }
293 }
294
get_geometry_data(const Mesh * mesh,USDMeshData & usd_mesh_data)295 void USDGenericMeshWriter::get_geometry_data(const Mesh *mesh, USDMeshData &usd_mesh_data)
296 {
297 get_vertices(mesh, usd_mesh_data);
298 get_loops_polys(mesh, usd_mesh_data);
299 get_creases(mesh, usd_mesh_data);
300 }
301
assign_materials(const HierarchyContext & context,pxr::UsdGeomMesh usd_mesh,const MaterialFaceGroups & usd_face_groups)302 void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
303 pxr::UsdGeomMesh usd_mesh,
304 const MaterialFaceGroups &usd_face_groups)
305 {
306 if (context.object->totcol == 0) {
307 return;
308 }
309
310 /* Binding a material to a geometry subset isn't supported by the Hydra GL viewport yet,
311 * which is why we always bind the first material to the entire mesh. See
312 * https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */
313 bool mesh_material_bound = false;
314 pxr::UsdShadeMaterialBindingAPI material_binding_api(usd_mesh.GetPrim());
315 for (int mat_num = 0; mat_num < context.object->totcol; mat_num++) {
316 Material *material = BKE_object_material_get(context.object, mat_num + 1);
317 if (material == nullptr) {
318 continue;
319 }
320
321 pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
322 material_binding_api.Bind(usd_material);
323
324 /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
325 * use the flag from the first non-empty material slot. */
326 usd_mesh.CreateDoubleSidedAttr(
327 pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
328
329 mesh_material_bound = true;
330 break;
331 }
332
333 if (!mesh_material_bound) {
334 /* Blender defaults to double-sided, but USD to single-sided. */
335 usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true));
336 }
337
338 if (!mesh_material_bound || usd_face_groups.size() < 2) {
339 /* Either all material slots were empty or there is only one material in use. As geometry
340 * subsets are only written when actually used to assign a material, and the mesh already has
341 * the material assigned, there is no need to continue. */
342 return;
343 }
344
345 /* Define a geometry subset per material. */
346 for (const MaterialFaceGroups::value_type &face_group : usd_face_groups) {
347 short material_number = face_group.first;
348 const pxr::VtIntArray &face_indices = face_group.second;
349
350 Material *material = BKE_object_material_get(context.object, material_number + 1);
351 if (material == nullptr) {
352 continue;
353 }
354
355 pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
356 pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
357
358 pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
359 material_name, face_indices);
360 pxr::UsdShadeMaterialBindingAPI(usd_face_subset.GetPrim()).Bind(usd_material);
361 }
362 }
363
write_normals(const Mesh * mesh,pxr::UsdGeomMesh usd_mesh)364 void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
365 {
366 pxr::UsdTimeCode timecode = get_export_time_code();
367 const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL));
368
369 pxr::VtVec3fArray loop_normals;
370 loop_normals.reserve(mesh->totloop);
371
372 if (lnors != nullptr) {
373 /* Export custom loop normals. */
374 for (int loop_idx = 0, totloop = mesh->totloop; loop_idx < totloop; ++loop_idx) {
375 loop_normals.push_back(pxr::GfVec3f(lnors[loop_idx]));
376 }
377 }
378 else {
379 /* Compute the loop normals based on the 'smooth' flag. */
380 float normal[3];
381 MPoly *mpoly = mesh->mpoly;
382 const MVert *mvert = mesh->mvert;
383 for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
384 MLoop *mloop = mesh->mloop + mpoly->loopstart;
385
386 if ((mpoly->flag & ME_SMOOTH) == 0) {
387 /* Flat shaded, use common normal for all verts. */
388 BKE_mesh_calc_poly_normal(mpoly, mloop, mvert, normal);
389 pxr::GfVec3f pxr_normal(normal);
390 for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx) {
391 loop_normals.push_back(pxr_normal);
392 }
393 }
394 else {
395 /* Smooth shaded, use individual vert normals. */
396 for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) {
397 normal_short_to_float_v3(normal, mvert[mloop->v].no);
398 loop_normals.push_back(pxr::GfVec3f(normal));
399 }
400 }
401 }
402 }
403
404 pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
405 if (!attr_normals.HasValue()) {
406 attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
407 }
408 usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
409 usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
410 }
411
write_surface_velocity(Object * object,const Mesh * mesh,pxr::UsdGeomMesh usd_mesh)412 void USDGenericMeshWriter::write_surface_velocity(Object *object,
413 const Mesh *mesh,
414 pxr::UsdGeomMesh usd_mesh)
415 {
416 /* Only velocities from the fluid simulation are exported. This is the most important case,
417 * though, as the baked mesh changes topology all the time, and thus computing the velocities
418 * at import time in a post-processing step is hard. */
419 ModifierData *md = BKE_modifiers_findby_type(object, eModifierType_Fluidsim);
420 if (md == nullptr) {
421 return;
422 }
423
424 /* Check that the fluid sim modifier is enabled and has useful data. */
425 const bool use_render = (DEG_get_mode(usd_export_context_.depsgraph) == DAG_EVAL_RENDER);
426 const ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
427 const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
428 if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
429 return;
430 }
431 FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
432 if (!fsmd->fss || fsmd->fss->type != OB_FLUIDSIM_DOMAIN) {
433 return;
434 }
435 FluidsimSettings *fss = fsmd->fss;
436 if (!fss->meshVelocities) {
437 return;
438 }
439
440 /* Export per-vertex velocity vectors. */
441 pxr::VtVec3fArray usd_velocities;
442 usd_velocities.reserve(mesh->totvert);
443
444 FluidVertexVelocity *mesh_velocities = fss->meshVelocities;
445 for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert;
446 ++vertex_idx, ++mesh_velocities) {
447 usd_velocities.push_back(pxr::GfVec3f(mesh_velocities->vel));
448 }
449
450 pxr::UsdTimeCode timecode = get_export_time_code();
451 usd_mesh.CreateVelocitiesAttr().Set(usd_velocities, timecode);
452 }
453
USDMeshWriter(const USDExporterContext & ctx)454 USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx)
455 {
456 }
457
get_export_mesh(Object * object_eval,bool &)458 Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
459 {
460 return BKE_object_get_evaluated_mesh(object_eval);
461 }
462
463 } // namespace blender::io::usd
464