2023-08-16 00:20:26 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
2023-05-31 16:19:06 +02:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
2021-08-03 11:55:53 +02:00
|
|
|
* Adapted from the Blender Alembic importer implementation.
|
2022-02-10 09:56:54 +11:00
|
|
|
* Modifications Copyright 2021 Tangent Animation and
|
|
|
|
|
* NVIDIA Corporation. All rights reserved. */
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
#include "usd_reader_mesh.h"
|
|
|
|
|
#include "usd_reader_material.h"
|
2023-08-17 20:11:51 +02:00
|
|
|
#include "usd_skel_convert.h"
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2022-08-31 09:09:01 -05:00
|
|
|
#include "BKE_attribute.hh"
|
2021-08-03 11:55:53 +02:00
|
|
|
#include "BKE_customdata.h"
|
|
|
|
|
#include "BKE_main.h"
|
|
|
|
|
#include "BKE_material.h"
|
2023-03-12 22:29:15 +01:00
|
|
|
#include "BKE_mesh.hh"
|
2023-10-09 23:41:53 +02:00
|
|
|
#include "BKE_object.hh"
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-08-13 10:50:52 +03:00
|
|
|
#include "BLI_math_color.hh"
|
2021-08-03 11:55:53 +02:00
|
|
|
#include "BLI_math_geom.h"
|
2023-01-04 00:14:55 +01:00
|
|
|
#include "BLI_math_vector_types.hh"
|
Refactor: Move normals out of MVert, lazy calculation
As described in T91186, this commit moves mesh vertex normals into a
contiguous array of float vectors in a custom data layer, how face
normals are currently stored.
The main interface is documented in `BKE_mesh.h`. Vertex and face
normals are now calculated on-demand and cached, retrieved with an
"ensure" function. Since the logical state of a mesh is now "has
normals when necessary", they can be retrieved from a `const` mesh.
The goal is to use on-demand calculation for all derived data, but
leave room for eager calculation for performance purposes (modifier
evaluation is threaded, but viewport data generation is not).
**Benefits**
This moves us closer to a SoA approach rather than the current AoS
paradigm. Accessing a contiguous `float3` is much more efficient than
retrieving data from a larger struct. The memory requirements for
accessing only normals or vertex locations are smaller, and at the
cost of more memory usage for just normals, they now don't have to
be converted between float and short, which also simplifies code
In the future, the remaining items can be removed from `MVert`,
leaving only `float3`, which has similar benefits (see T93602).
Removing the combination of derived and original data makes it
conceptually simpler to only calculate normals when necessary.
This is especially important now that we have more opportunities
for temporary meshes in geometry nodes.
**Performance**
In addition to the theoretical future performance improvements by
making `MVert == float3`, I've done some basic performance testing
on this patch directly. The data is fairly rough, but it gives an idea
about where things stand generally.
- Mesh line primitive 4m Verts: 1.16x faster (36 -> 31 ms),
showing that accessing just `MVert` is now more efficient.
- Spring Splash Screen: 1.03-1.06 -> 1.06-1.11 FPS, a very slight
change that at least shows there is no regression.
- Sprite Fright Snail Smoosh: 3.30-3.40 -> 3.42-3.50 FPS, a small
but observable speedup.
- Set Position Node with Scaled Normal: 1.36x faster (53 -> 39 ms),
shows that using normals in geometry nodes is faster.
- Normal Calculation 1.6m Vert Cube: 1.19x faster (25 -> 21 ms),
shows that calculating normals is slightly faster now.
- File Size of 1.6m Vert Cube: 1.03x smaller (214.7 -> 208.4 MB),
Normals are not saved in files, which can help with large meshes.
As for memory usage, it may be slightly more in some cases, but
I didn't observe any difference in the production files I tested.
**Tests**
Some modifiers and cycles test results need to be updated with this
commit, for two reasons:
- The subdivision surface modifier is not responsible for calculating
normals anymore. In master, the modifier creates different normals
than the result of the `Mesh` normal calculation, so this is a bug
fix.
- There are small differences in the results of some modifiers that
use normals because they are not converted to and from `short`
anymore.
**Future improvements**
- Remove `ModifierTypeInfo::dependsOnNormals`. Code in each modifier
already retrieves normals if they are needed anyway.
- Copy normals as part of a better CoW system for attributes.
- Make more areas use lazy instead of eager normal calculation.
- Remove `BKE_mesh_normals_tag_dirty` in more places since that is
now the default state of a new mesh.
- Possibly apply a similar change to derived face corner normals.
Differential Revision: https://developer.blender.org/D12770
2022-01-13 14:37:58 -06:00
|
|
|
#include "BLI_span.hh"
|
2021-08-03 11:55:53 +02:00
|
|
|
#include "BLI_string.h"
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
#include "usd_hash_types.h"
|
|
|
|
|
|
2021-08-03 11:55:53 +02:00
|
|
|
#include "DNA_customdata_types.h"
|
|
|
|
|
#include "DNA_material_types.h"
|
|
|
|
|
#include "DNA_mesh_types.h"
|
|
|
|
|
#include "DNA_meshdata_types.h"
|
|
|
|
|
#include "DNA_modifier_types.h"
|
|
|
|
|
#include "DNA_object_types.h"
|
2023-04-14 11:05:26 +02:00
|
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
|
|
2023-08-04 23:11:22 +02:00
|
|
|
#include "WM_api.hh"
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
|
|
|
|
#include <pxr/base/vt/array.h>
|
|
|
|
|
#include <pxr/base/vt/types.h>
|
|
|
|
|
#include <pxr/base/vt/value.h>
|
|
|
|
|
#include <pxr/usd/sdf/types.h>
|
|
|
|
|
#include <pxr/usd/usdGeom/mesh.h>
|
2022-11-01 11:55:58 -04:00
|
|
|
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
2021-08-03 11:55:53 +02:00
|
|
|
#include <pxr/usd/usdGeom/subset.h>
|
|
|
|
|
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
2023-08-17 20:11:51 +02:00
|
|
|
#include <pxr/usd/usdSkel/bindingAPI.h>
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
namespace usdtokens {
|
|
|
|
|
/* Materials */
|
|
|
|
|
static const pxr::TfToken st("st", pxr::TfToken::Immortal);
|
|
|
|
|
static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal);
|
|
|
|
|
static const pxr::TfToken Cd("Cd", pxr::TfToken::Immortal);
|
|
|
|
|
static const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal);
|
|
|
|
|
static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal);
|
|
|
|
|
} // namespace usdtokens
|
|
|
|
|
|
|
|
|
|
namespace utils {
|
|
|
|
|
|
2022-06-10 10:12:41 -04:00
|
|
|
static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim)
|
|
|
|
|
{
|
2022-08-01 12:14:05 -04:00
|
|
|
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim);
|
|
|
|
|
|
|
|
|
|
/* Compute generically bound ('allPurpose') materials. */
|
|
|
|
|
pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial();
|
|
|
|
|
|
|
|
|
|
/* If no generic material could be resolved, also check for 'preview' and
|
|
|
|
|
* 'full' purpose materials as fallbacks. */
|
|
|
|
|
if (!mtl) {
|
|
|
|
|
mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!mtl) {
|
|
|
|
|
mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->full);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mtl;
|
2022-06-10 10:12:41 -04:00
|
|
|
}
|
|
|
|
|
|
2021-08-03 11:55:53 +02:00
|
|
|
static void assign_materials(Main *bmain,
|
|
|
|
|
Object *ob,
|
|
|
|
|
const std::map<pxr::SdfPath, int> &mat_index_map,
|
|
|
|
|
const USDImportParams ¶ms,
|
2022-06-10 10:12:41 -04:00
|
|
|
pxr::UsdStageRefPtr stage,
|
2022-06-17 22:28:22 +03:00
|
|
|
std::map<std::string, Material *> &mat_name_to_mat,
|
2022-06-10 10:12:41 -04:00
|
|
|
std::map<std::string, std::string> &usd_path_to_mat_name)
|
2021-08-03 11:55:53 +02:00
|
|
|
{
|
|
|
|
|
if (!(stage && bmain && ob)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
IO: speed up import of large Alembic/USD/OBJ scenes by optimizing material assignment
The importer parts that were doing assignment of materials to the
imported objects/meshes were essentially having a quadratic complexity
in terms of scene object count. For each material assigned to each
object, they were scanning the whole scene, checking which other
Objects use the same Mesh data, in order to resize their material
arrays to match the size.
Performance details (Windows, Ryzen 5950X):
- Import OBJ Blender 3.0 splash scene (24k objects): 43.0s -> 32.9s
- Import USD Disney Moana scene (260k objects): saves two hours
(~7400s). Note that later on this crashes when trying to render the
imported result; crashes in the same way/place both in master and
this patch.
Implementation details:
The importers were doing "scan the world" basically twice for each
object, for each material: once when creating a new material slot
(assigns an empty material), and then again when assigning the
material.
However, all these importers (USD, Alembic, OBJ) always create one
Object for one Mesh. So that whole quadratic complexity resulting
from "scan the world for possible other users of this obdata" is
completely not needed; it just never finds anything. So add a new
dedicated function BKE_object_material_assign_single_obdata that skips
the expensive part, but should only be used when the caller knows that
the obdata has exactly one user (the passed object).
Reviewed By: Bastien Montagne, Michael Kowalski
Differential Revision: https://developer.blender.org/D15145
2022-07-06 13:29:59 +03:00
|
|
|
if (mat_index_map.size() > MAXMAT) {
|
2021-08-03 11:55:53 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blender::io::usd::USDMaterialReader mat_reader(params, bmain);
|
|
|
|
|
|
IO: speed up import of large Alembic/USD/OBJ scenes by optimizing material assignment
The importer parts that were doing assignment of materials to the
imported objects/meshes were essentially having a quadratic complexity
in terms of scene object count. For each material assigned to each
object, they were scanning the whole scene, checking which other
Objects use the same Mesh data, in order to resize their material
arrays to match the size.
Performance details (Windows, Ryzen 5950X):
- Import OBJ Blender 3.0 splash scene (24k objects): 43.0s -> 32.9s
- Import USD Disney Moana scene (260k objects): saves two hours
(~7400s). Note that later on this crashes when trying to render the
imported result; crashes in the same way/place both in master and
this patch.
Implementation details:
The importers were doing "scan the world" basically twice for each
object, for each material: once when creating a new material slot
(assigns an empty material), and then again when assigning the
material.
However, all these importers (USD, Alembic, OBJ) always create one
Object for one Mesh. So that whole quadratic complexity resulting
from "scan the world for possible other users of this obdata" is
completely not needed; it just never finds anything. So add a new
dedicated function BKE_object_material_assign_single_obdata that skips
the expensive part, but should only be used when the caller knows that
the obdata has exactly one user (the passed object).
Reviewed By: Bastien Montagne, Michael Kowalski
Differential Revision: https://developer.blender.org/D15145
2022-07-06 13:29:59 +03:00
|
|
|
for (std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin();
|
|
|
|
|
it != mat_index_map.end();
|
|
|
|
|
++it)
|
|
|
|
|
{
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-01-25 10:46:07 -05:00
|
|
|
Material *assigned_mat = blender::io::usd::find_existing_material(
|
2022-06-17 22:28:22 +03:00
|
|
|
it->first, params, mat_name_to_mat, usd_path_to_mat_name);
|
2022-06-10 10:12:41 -04:00
|
|
|
if (!assigned_mat) {
|
2021-08-03 11:55:53 +02:00
|
|
|
/* Blender material doesn't exist, so create it now. */
|
|
|
|
|
|
|
|
|
|
/* Look up the USD material. */
|
|
|
|
|
pxr::UsdPrim prim = stage->GetPrimAtPath(it->first);
|
|
|
|
|
pxr::UsdShadeMaterial usd_mat(prim);
|
|
|
|
|
|
|
|
|
|
if (!usd_mat) {
|
|
|
|
|
std::cout << "WARNING: Couldn't construct USD material from prim " << it->first
|
|
|
|
|
<< std::endl;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add the Blender material. */
|
|
|
|
|
assigned_mat = mat_reader.add_material(usd_mat);
|
|
|
|
|
|
|
|
|
|
if (!assigned_mat) {
|
|
|
|
|
std::cout << "WARNING: Couldn't create Blender material from USD material " << it->first
|
|
|
|
|
<< std::endl;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 10:12:41 -04:00
|
|
|
const std::string mat_name = pxr::TfMakeValidIdentifier(assigned_mat->id.name + 2);
|
2022-06-17 22:28:22 +03:00
|
|
|
mat_name_to_mat[mat_name] = assigned_mat;
|
2022-06-10 10:12:41 -04:00
|
|
|
|
|
|
|
|
if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) {
|
|
|
|
|
/* Record the name of the Blender material we created for the USD material
|
|
|
|
|
* with the given path. */
|
|
|
|
|
usd_path_to_mat_name[it->first.GetAsString()] = mat_name;
|
|
|
|
|
}
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (assigned_mat) {
|
IO: speed up import of large Alembic/USD/OBJ scenes by optimizing material assignment
The importer parts that were doing assignment of materials to the
imported objects/meshes were essentially having a quadratic complexity
in terms of scene object count. For each material assigned to each
object, they were scanning the whole scene, checking which other
Objects use the same Mesh data, in order to resize their material
arrays to match the size.
Performance details (Windows, Ryzen 5950X):
- Import OBJ Blender 3.0 splash scene (24k objects): 43.0s -> 32.9s
- Import USD Disney Moana scene (260k objects): saves two hours
(~7400s). Note that later on this crashes when trying to render the
imported result; crashes in the same way/place both in master and
this patch.
Implementation details:
The importers were doing "scan the world" basically twice for each
object, for each material: once when creating a new material slot
(assigns an empty material), and then again when assigning the
material.
However, all these importers (USD, Alembic, OBJ) always create one
Object for one Mesh. So that whole quadratic complexity resulting
from "scan the world for possible other users of this obdata" is
completely not needed; it just never finds anything. So add a new
dedicated function BKE_object_material_assign_single_obdata that skips
the expensive part, but should only be used when the caller knows that
the obdata has exactly one user (the passed object).
Reviewed By: Bastien Montagne, Michael Kowalski
Differential Revision: https://developer.blender.org/D15145
2022-07-06 13:29:59 +03:00
|
|
|
BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, it->second);
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* This shouldn't happen. */
|
2022-06-10 10:12:41 -04:00
|
|
|
std::cout << "WARNING: Couldn't assign material " << it->first << std::endl;
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-09-01 20:38:56 +03:00
|
|
|
if (ob->totcol > 0) {
|
|
|
|
|
ob->actcol = 1;
|
|
|
|
|
}
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace utils
|
|
|
|
|
|
|
|
|
|
namespace blender::io::usd {
|
|
|
|
|
|
|
|
|
|
USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim,
|
|
|
|
|
const USDImportParams &import_params,
|
|
|
|
|
const ImportSettings &settings)
|
|
|
|
|
: USDGeomReader(prim, import_params, settings),
|
|
|
|
|
mesh_prim_(prim),
|
|
|
|
|
is_left_handed_(false),
|
|
|
|
|
is_time_varying_(false),
|
|
|
|
|
is_initial_load_(false)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
static std::optional<eCustomDataType> convert_usd_type_to_blender(
|
|
|
|
|
const pxr::SdfValueTypeName usd_type)
|
|
|
|
|
{
|
|
|
|
|
static const blender::Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() {
|
|
|
|
|
blender::Map<pxr::SdfValueTypeName, eCustomDataType> map;
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->FloatArray, CD_PROP_FLOAT);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Double, CD_PROP_FLOAT);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->IntArray, CD_PROP_INT32);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Float2Array, CD_PROP_FLOAT2);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->TexCoord2dArray, CD_PROP_FLOAT2);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->TexCoord2fArray, CD_PROP_FLOAT2);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->TexCoord2hArray, CD_PROP_FLOAT2);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->TexCoord3dArray, CD_PROP_FLOAT2);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->TexCoord3fArray, CD_PROP_FLOAT2);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->TexCoord3hArray, CD_PROP_FLOAT2);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Float3Array, CD_PROP_FLOAT3);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Vector3fArray, CD_PROP_FLOAT3);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Vector3hArray, CD_PROP_FLOAT3);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Vector3dArray, CD_PROP_FLOAT3);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Color3fArray, CD_PROP_COLOR);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Color3hArray, CD_PROP_COLOR);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->Color3dArray, CD_PROP_COLOR);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->StringArray, CD_PROP_STRING);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->BoolArray, CD_PROP_BOOL);
|
|
|
|
|
map.add_new(pxr::SdfValueTypeNames->QuatfArray, CD_PROP_QUATERNION);
|
|
|
|
|
return map;
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
const eCustomDataType *value = type_map.lookup_ptr(usd_type);
|
|
|
|
|
if (value == nullptr) {
|
2023-08-14 15:51:00 -04:00
|
|
|
WM_reportf(RPT_WARNING, "Unsupported type %s for mesh data", usd_type.GetAsToken().GetText());
|
2023-08-11 23:47:17 +02:00
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return *value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const std::optional<eAttrDomain> convert_usd_varying_to_blender(
|
|
|
|
|
const pxr::TfToken usd_domain)
|
|
|
|
|
{
|
|
|
|
|
static const blender::Map<pxr::TfToken, eAttrDomain> domain_map = []() {
|
|
|
|
|
blender::Map<pxr::TfToken, eAttrDomain> map;
|
|
|
|
|
map.add_new(pxr::UsdGeomTokens->faceVarying, ATTR_DOMAIN_CORNER);
|
|
|
|
|
map.add_new(pxr::UsdGeomTokens->vertex, ATTR_DOMAIN_POINT);
|
|
|
|
|
map.add_new(pxr::UsdGeomTokens->varying, ATTR_DOMAIN_POINT);
|
|
|
|
|
map.add_new(pxr::UsdGeomTokens->face, ATTR_DOMAIN_FACE);
|
|
|
|
|
/* As there's no "constant" type in Blender, for now we're
|
|
|
|
|
* translating into a point Attribute. */
|
|
|
|
|
map.add_new(pxr::UsdGeomTokens->constant, ATTR_DOMAIN_POINT);
|
|
|
|
|
map.add_new(pxr::UsdGeomTokens->uniform, ATTR_DOMAIN_FACE);
|
|
|
|
|
/* Notice: Edge types are not supported! */
|
|
|
|
|
return map;
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
const eAttrDomain *value = domain_map.lookup_ptr(usd_domain);
|
|
|
|
|
|
|
|
|
|
if (value == nullptr) {
|
|
|
|
|
WM_reportf(RPT_WARNING, "Unsupported domain for mesh data type %s", usd_domain.GetText());
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return *value;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-22 12:02:32 +10:00
|
|
|
void USDMeshReader::create_object(Main *bmain, const double /*motionSampleTime*/)
|
2021-08-03 11:55:53 +02:00
|
|
|
{
|
|
|
|
|
Mesh *mesh = BKE_mesh_add(bmain, name_.c_str());
|
|
|
|
|
|
|
|
|
|
object_ = BKE_object_add_only_object(bmain, OB_MESH, name_.c_str());
|
|
|
|
|
object_->data = mesh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime)
|
|
|
|
|
{
|
|
|
|
|
Mesh *mesh = (Mesh *)object_->data;
|
|
|
|
|
|
|
|
|
|
is_initial_load_ = true;
|
2023-02-26 11:55:22 +13:00
|
|
|
const USDMeshReadParams params = create_mesh_read_params(motionSampleTime,
|
|
|
|
|
import_params_.mesh_read_flag);
|
2023-02-22 11:22:48 -05:00
|
|
|
|
|
|
|
|
Mesh *read_mesh = this->read_mesh(mesh, params, nullptr);
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
is_initial_load_ = false;
|
|
|
|
|
if (read_mesh != mesh) {
|
2022-09-09 08:24:31 -05:00
|
|
|
BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_);
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readFaceSetsSample(bmain, mesh, motionSampleTime);
|
|
|
|
|
|
|
|
|
|
if (mesh_prim_.GetPointsAttr().ValueMightBeTimeVarying()) {
|
|
|
|
|
is_time_varying_ = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_time_varying_) {
|
|
|
|
|
add_cache_modifier();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (import_params_.import_subdiv) {
|
|
|
|
|
pxr::TfToken subdivScheme;
|
|
|
|
|
mesh_prim_.GetSubdivisionSchemeAttr().Get(&subdivScheme, motionSampleTime);
|
|
|
|
|
|
|
|
|
|
if (subdivScheme == pxr::UsdGeomTokens->catmullClark) {
|
|
|
|
|
add_subdiv_modifier();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-17 20:11:51 +02:00
|
|
|
if (import_params_.import_blendshapes) {
|
|
|
|
|
import_blendshapes(bmain, object_, prim_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (import_params_.import_skeletons) {
|
|
|
|
|
import_mesh_skel_bindings(bmain, object_, prim_);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-03 11:55:53 +02:00
|
|
|
USDXformReader::read_object_data(bmain, motionSampleTime);
|
2023-02-22 11:22:48 -05:00
|
|
|
} // namespace blender::io::usd
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
bool USDMeshReader::valid() const
|
|
|
|
|
{
|
2022-09-25 18:30:50 +10:00
|
|
|
return bool(mesh_prim_);
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-23 21:24:50 +01:00
|
|
|
bool USDMeshReader::topology_changed(const Mesh *existing_mesh, const double motionSampleTime)
|
2021-08-03 11:55:53 +02:00
|
|
|
{
|
|
|
|
|
/* TODO(makowalski): Is it the best strategy to cache the mesh
|
|
|
|
|
* geometry in this function? This needs to be revisited. */
|
|
|
|
|
|
|
|
|
|
mesh_prim_.GetFaceVertexIndicesAttr().Get(&face_indices_, motionSampleTime);
|
|
|
|
|
mesh_prim_.GetFaceVertexCountsAttr().Get(&face_counts_, motionSampleTime);
|
|
|
|
|
mesh_prim_.GetPointsAttr().Get(&positions_, motionSampleTime);
|
|
|
|
|
|
2022-11-01 11:55:58 -04:00
|
|
|
pxr::UsdGeomPrimvarsAPI primvarsAPI(mesh_prim_);
|
|
|
|
|
|
2021-08-03 11:55:53 +02:00
|
|
|
/* TODO(makowalski): Reading normals probably doesn't belong in this function,
|
|
|
|
|
* as this is not required to determine if the topology has changed. */
|
|
|
|
|
|
|
|
|
|
/* If 'normals' and 'primvars:normals' are both specified, the latter has precedence. */
|
2022-11-01 11:55:58 -04:00
|
|
|
pxr::UsdGeomPrimvar primvar = primvarsAPI.GetPrimvar(usdtokens::normalsPrimvar);
|
2021-08-03 11:55:53 +02:00
|
|
|
if (primvar.HasValue()) {
|
|
|
|
|
primvar.ComputeFlattened(&normals_, motionSampleTime);
|
|
|
|
|
normal_interpolation_ = primvar.GetInterpolation();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
mesh_prim_.GetNormalsAttr().Get(&normals_, motionSampleTime);
|
|
|
|
|
normal_interpolation_ = mesh_prim_.GetNormalsInterpolation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return positions_.size() != existing_mesh->totvert ||
|
2023-07-24 22:06:55 +02:00
|
|
|
face_counts_.size() != existing_mesh->faces_num ||
|
2021-08-03 11:55:53 +02:00
|
|
|
face_indices_.size() != existing_mesh->totloop;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::read_mpolys(Mesh *mesh)
|
|
|
|
|
{
|
2023-07-24 22:06:55 +02:00
|
|
|
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
|
Mesh: Replace MLoop struct with generic attributes
Implements #102359.
Split the `MLoop` struct into two separate integer arrays called
`corner_verts` and `corner_edges`, referring to the vertex each corner
is attached to and the next edge around the face at each corner. These
arrays can be sliced to give access to the edges or vertices in a face.
Then they are often referred to as "poly_verts" or "poly_edges".
The main benefits are halving the necessary memory bandwidth when only
one array is used and simplifications from using regular integer indices
instead of a special-purpose struct.
The commit also starts a renaming from "loop" to "corner" in mesh code.
Like the other mesh struct of array refactors, forward compatibility is
kept by writing files with the older format. This will be done until 4.0
to ease the transition process.
Looking at a small portion of the patch should give a good impression
for the rest of the changes. I tried to make the changes as small as
possible so it's easy to tell the correctness from the diff. Though I
found Blender developers have been very inventive over the last decade
when finding different ways to loop over the corners in a face.
For performance, nearly every piece of code that deals with `Mesh` is
slightly impacted. Any algorithm that is memory bottle-necked should
see an improvement. For example, here is a comparison of interpolating
a vertex float attribute to face corners (Ryzen 3700x):
**Before** (Average: 3.7 ms, Min: 3.4 ms)
```
threading::parallel_for(loops.index_range(), 4096, [&](IndexRange range) {
for (const int64_t i : range) {
dst[i] = src[loops[i].v];
}
});
```
**After** (Average: 2.9 ms, Min: 2.6 ms)
```
array_utils::gather(src, corner_verts, dst);
```
That's an improvement of 28% to the average timings, and it's also a
simplification, since an index-based routine can be used instead.
For more examples using the new arrays, see the design task.
Pull Request: https://projects.blender.org/blender/blender/pulls/104424
2023-03-20 15:55:13 +01:00
|
|
|
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
int loop_index = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < face_counts_.size(); i++) {
|
|
|
|
|
const int face_size = face_counts_[i];
|
|
|
|
|
|
2023-07-24 22:06:55 +02:00
|
|
|
face_offsets[i] = loop_index;
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
/* Polygons are always assumed to be smooth-shaded. If the mesh should be flat-shaded,
|
|
|
|
|
* this is encoded in custom loop normals. */
|
|
|
|
|
|
|
|
|
|
if (is_left_handed_) {
|
|
|
|
|
int loop_end_index = loop_index + (face_size - 1);
|
|
|
|
|
for (int f = 0; f < face_size; ++f, ++loop_index) {
|
Mesh: Replace MLoop struct with generic attributes
Implements #102359.
Split the `MLoop` struct into two separate integer arrays called
`corner_verts` and `corner_edges`, referring to the vertex each corner
is attached to and the next edge around the face at each corner. These
arrays can be sliced to give access to the edges or vertices in a face.
Then they are often referred to as "poly_verts" or "poly_edges".
The main benefits are halving the necessary memory bandwidth when only
one array is used and simplifications from using regular integer indices
instead of a special-purpose struct.
The commit also starts a renaming from "loop" to "corner" in mesh code.
Like the other mesh struct of array refactors, forward compatibility is
kept by writing files with the older format. This will be done until 4.0
to ease the transition process.
Looking at a small portion of the patch should give a good impression
for the rest of the changes. I tried to make the changes as small as
possible so it's easy to tell the correctness from the diff. Though I
found Blender developers have been very inventive over the last decade
when finding different ways to loop over the corners in a face.
For performance, nearly every piece of code that deals with `Mesh` is
slightly impacted. Any algorithm that is memory bottle-necked should
see an improvement. For example, here is a comparison of interpolating
a vertex float attribute to face corners (Ryzen 3700x):
**Before** (Average: 3.7 ms, Min: 3.4 ms)
```
threading::parallel_for(loops.index_range(), 4096, [&](IndexRange range) {
for (const int64_t i : range) {
dst[i] = src[loops[i].v];
}
});
```
**After** (Average: 2.9 ms, Min: 2.6 ms)
```
array_utils::gather(src, corner_verts, dst);
```
That's an improvement of 28% to the average timings, and it's also a
simplification, since an index-based routine can be used instead.
For more examples using the new arrays, see the design task.
Pull Request: https://projects.blender.org/blender/blender/pulls/104424
2023-03-20 15:55:13 +01:00
|
|
|
corner_verts[loop_index] = face_indices_[loop_end_index - f];
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (int f = 0; f < face_size; ++f, ++loop_index) {
|
Mesh: Replace MLoop struct with generic attributes
Implements #102359.
Split the `MLoop` struct into two separate integer arrays called
`corner_verts` and `corner_edges`, referring to the vertex each corner
is attached to and the next edge around the face at each corner. These
arrays can be sliced to give access to the edges or vertices in a face.
Then they are often referred to as "poly_verts" or "poly_edges".
The main benefits are halving the necessary memory bandwidth when only
one array is used and simplifications from using regular integer indices
instead of a special-purpose struct.
The commit also starts a renaming from "loop" to "corner" in mesh code.
Like the other mesh struct of array refactors, forward compatibility is
kept by writing files with the older format. This will be done until 4.0
to ease the transition process.
Looking at a small portion of the patch should give a good impression
for the rest of the changes. I tried to make the changes as small as
possible so it's easy to tell the correctness from the diff. Though I
found Blender developers have been very inventive over the last decade
when finding different ways to loop over the corners in a face.
For performance, nearly every piece of code that deals with `Mesh` is
slightly impacted. Any algorithm that is memory bottle-necked should
see an improvement. For example, here is a comparison of interpolating
a vertex float attribute to face corners (Ryzen 3700x):
**Before** (Average: 3.7 ms, Min: 3.4 ms)
```
threading::parallel_for(loops.index_range(), 4096, [&](IndexRange range) {
for (const int64_t i : range) {
dst[i] = src[loops[i].v];
}
});
```
**After** (Average: 2.9 ms, Min: 2.6 ms)
```
array_utils::gather(src, corner_verts, dst);
```
That's an improvement of 28% to the average timings, and it's also a
simplification, since an index-based routine can be used instead.
For more examples using the new arrays, see the design task.
Pull Request: https://projects.blender.org/blender/blender/pulls/104424
2023-03-20 15:55:13 +01:00
|
|
|
corner_verts[loop_index] = face_indices_[loop_index];
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BKE_mesh_calc_edges(mesh, false, false);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
template<typename T>
|
|
|
|
|
pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar,
|
|
|
|
|
const double motionSampleTime)
|
2021-08-03 11:55:53 +02:00
|
|
|
{
|
2023-08-11 23:47:17 +02:00
|
|
|
pxr::VtArray<T> array;
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
pxr::VtValue primvar_val;
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) {
|
|
|
|
|
WM_reportf(
|
|
|
|
|
RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText());
|
|
|
|
|
return array;
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
if (!primvar_val.CanCast<pxr::VtArray<T>>()) {
|
|
|
|
|
WM_reportf(RPT_WARNING,
|
|
|
|
|
"USD Import: can't cast attribute '%s' to array",
|
|
|
|
|
primvar.GetName().GetText());
|
|
|
|
|
return array;
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
array = primvar_val.Cast<pxr::VtArray<T>>().template UncheckedGet<pxr::VtArray<T>>();
|
|
|
|
|
return array;
|
2023-04-14 11:05:26 +02:00
|
|
|
}
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-04-14 11:05:26 +02:00
|
|
|
void USDMeshReader::read_color_data_primvar(Mesh *mesh,
|
2023-08-11 23:47:17 +02:00
|
|
|
const pxr::UsdGeomPrimvar &primvar,
|
2023-04-14 11:05:26 +02:00
|
|
|
const double motionSampleTime)
|
|
|
|
|
{
|
2023-08-11 23:47:17 +02:00
|
|
|
if (!(mesh && primvar && primvar.HasValue())) {
|
2021-08-03 11:55:53 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
pxr::VtArray<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(primvar,
|
|
|
|
|
motionSampleTime);
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
if (usd_colors.empty()) {
|
2021-08-03 11:55:53 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
pxr::TfToken interp = primvar.GetInterpolation();
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-04-14 11:05:26 +02:00
|
|
|
if ((interp == pxr::UsdGeomTokens->faceVarying && usd_colors.size() != mesh->totloop) ||
|
|
|
|
|
(interp == pxr::UsdGeomTokens->varying && usd_colors.size() != mesh->totloop) ||
|
|
|
|
|
(interp == pxr::UsdGeomTokens->vertex && usd_colors.size() != mesh->totvert) ||
|
|
|
|
|
(interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) ||
|
2023-07-24 22:06:55 +02:00
|
|
|
(interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->faces_num))
|
2023-04-14 11:05:26 +02:00
|
|
|
{
|
|
|
|
|
WM_reportf(RPT_WARNING,
|
|
|
|
|
"USD Import: color attribute value '%s' count inconsistent with interpolation type",
|
2023-08-11 23:47:17 +02:00
|
|
|
primvar.GetName().GetText());
|
2021-08-03 11:55:53 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
const StringRef primvar_name(primvar.GetBaseName().GetString());
|
2023-04-14 11:05:26 +02:00
|
|
|
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-04-14 11:05:26 +02:00
|
|
|
eAttrDomain color_domain = ATTR_DOMAIN_POINT;
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-04-14 11:05:26 +02:00
|
|
|
if (ELEM(interp,
|
|
|
|
|
pxr::UsdGeomTokens->varying,
|
|
|
|
|
pxr::UsdGeomTokens->faceVarying,
|
|
|
|
|
pxr::UsdGeomTokens->uniform))
|
|
|
|
|
{
|
|
|
|
|
color_domain = ATTR_DOMAIN_CORNER;
|
|
|
|
|
}
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-04-14 11:05:26 +02:00
|
|
|
bke::SpanAttributeWriter<ColorGeometry4f> color_data;
|
2023-08-11 23:47:17 +02:00
|
|
|
color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(primvar_name,
|
2023-04-14 11:05:26 +02:00
|
|
|
color_domain);
|
|
|
|
|
if (!color_data) {
|
|
|
|
|
WM_reportf(RPT_WARNING,
|
|
|
|
|
"USD Import: couldn't add color attribute '%s'",
|
2023-08-11 23:47:17 +02:00
|
|
|
primvar.GetBaseName().GetText());
|
2023-04-14 11:05:26 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
if (ELEM(interp, pxr::UsdGeomTokens->constant)) {
|
2023-04-14 11:05:26 +02:00
|
|
|
/* For situations where there's only a single item, flood fill the object. */
|
|
|
|
|
color_data.span.fill(
|
|
|
|
|
ColorGeometry4f(usd_colors[0][0], usd_colors[0][1], usd_colors[0][2], 1.0f));
|
|
|
|
|
}
|
2023-10-02 16:32:00 +02:00
|
|
|
/* Check for situations that allow for a straight-forward copy by index. */
|
|
|
|
|
else if (interp == pxr::UsdGeomTokens->vertex ||
|
|
|
|
|
(interp == pxr::UsdGeomTokens->faceVarying && !is_left_handed_))
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < usd_colors.size(); i++) {
|
|
|
|
|
ColorGeometry4f color = ColorGeometry4f(
|
|
|
|
|
usd_colors[i][0], usd_colors[i][1], usd_colors[i][2], 1.0f);
|
|
|
|
|
color_data.span[i] = color;
|
2023-04-14 11:05:26 +02:00
|
|
|
}
|
2023-10-02 16:32:00 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Catch all for the remaining cases. */
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-10-02 16:32:00 +02:00
|
|
|
/* Special case: we will expand uniform color into corner color.
|
2023-04-14 11:05:26 +02:00
|
|
|
* Uniforms in USD come through as single colors, face-varying. Since Blender does not
|
|
|
|
|
* support this particular combination for paintable color attributes, we convert the type
|
|
|
|
|
* here to make sure that the user gets the same visual result.
|
2023-10-02 16:32:00 +02:00
|
|
|
*/
|
|
|
|
|
const OffsetIndices faces = mesh->faces();
|
|
|
|
|
const Span<int> corner_verts = mesh->corner_verts();
|
|
|
|
|
for (const int i : faces.index_range()) {
|
|
|
|
|
const IndexRange &face = faces[i];
|
|
|
|
|
for (int j = 0; j < face.size(); ++j) {
|
|
|
|
|
int loop_index = face[j];
|
|
|
|
|
|
|
|
|
|
/* Default for constant interpolation. */
|
|
|
|
|
int usd_index = 0;
|
|
|
|
|
|
|
|
|
|
if (interp == pxr::UsdGeomTokens->vertex) {
|
|
|
|
|
usd_index = corner_verts[loop_index];
|
|
|
|
|
}
|
|
|
|
|
else if (interp == pxr::UsdGeomTokens->faceVarying) {
|
|
|
|
|
usd_index = face.start();
|
|
|
|
|
if (is_left_handed_) {
|
|
|
|
|
usd_index += face.size() - 1 - j;
|
2023-04-14 11:05:26 +02:00
|
|
|
}
|
2023-10-02 16:32:00 +02:00
|
|
|
else {
|
|
|
|
|
usd_index += j;
|
2023-04-14 11:05:26 +02:00
|
|
|
}
|
2023-10-02 16:32:00 +02:00
|
|
|
}
|
|
|
|
|
else if (interp == pxr::UsdGeomTokens->uniform) {
|
|
|
|
|
/* Uniform varying uses the face index. */
|
|
|
|
|
usd_index = i;
|
|
|
|
|
}
|
2023-04-14 11:05:26 +02:00
|
|
|
|
2023-10-02 16:32:00 +02:00
|
|
|
if (usd_index >= usd_colors.size()) {
|
|
|
|
|
continue;
|
2023-04-14 11:05:26 +02:00
|
|
|
}
|
2023-10-02 16:32:00 +02:00
|
|
|
|
|
|
|
|
ColorGeometry4f color = ColorGeometry4f(
|
|
|
|
|
usd_colors[usd_index][0], usd_colors[usd_index][1], usd_colors[usd_index][2], 1.0f);
|
|
|
|
|
color_data.span[loop_index] = color;
|
2023-04-14 11:05:26 +02:00
|
|
|
}
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
USD import fix: set active mesh color.
Fixed a bug where the active color wasn't being set
on imported meshes, resulting in no colors displaying
in the viewport.
This bug has been in the code for a long time. However,
the colors have been displaying correctly until recently,
so this issue wasn't previously apparent.
Also, changed custom color data name from "displayColors"
to "displayColor", to match the actual USD primvar name.
(This was a typo in the original code.)
Note that pull request
https://projects.blender.org/blender/blender/pulls/104542
addresses other issues in the color import code (e.g.,
converting all color primvars and not just "displayColor",
avoiding hard-coding of attribute names, handling all
iterpolation types, etc.).
However, the current commit is meant as a short term fix
to a regression, where the "displayColor" attribute does
not render in the viewport at all, until the above pull
can be merged.
2023-03-20 11:14:56 -04:00
|
|
|
|
2023-04-14 11:05:26 +02:00
|
|
|
color_data.finish();
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
|
|
|
|
|
const pxr::UsdGeomPrimvar &primvar,
|
|
|
|
|
const double motionSampleTime)
|
|
|
|
|
{
|
|
|
|
|
const StringRef primvar_name(primvar.StripPrimvarsName(primvar.GetName()).GetString());
|
|
|
|
|
|
|
|
|
|
pxr::VtArray<pxr::GfVec2f> usd_uvs = get_prim_attribute_array<pxr::GfVec2f>(primvar,
|
|
|
|
|
motionSampleTime);
|
|
|
|
|
|
|
|
|
|
if (usd_uvs.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pxr::TfToken varying_type = primvar.GetInterpolation();
|
|
|
|
|
BLI_assert(ELEM(varying_type,
|
|
|
|
|
pxr::UsdGeomTokens->vertex,
|
|
|
|
|
pxr::UsdGeomTokens->faceVarying,
|
|
|
|
|
pxr::UsdGeomTokens->varying));
|
|
|
|
|
|
|
|
|
|
if ((varying_type == pxr::UsdGeomTokens->faceVarying && usd_uvs.size() != mesh->totloop) ||
|
|
|
|
|
(varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->totvert) ||
|
|
|
|
|
(varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->totloop))
|
|
|
|
|
{
|
|
|
|
|
WM_reportf(RPT_WARNING,
|
|
|
|
|
"USD Import: UV attribute value '%s' count inconsistent with interpolation type",
|
|
|
|
|
primvar.GetName().GetText());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
|
|
|
bke::SpanAttributeWriter<float2> uv_data = attributes.lookup_or_add_for_write_only_span<float2>(
|
|
|
|
|
primvar_name, ATTR_DOMAIN_CORNER);
|
|
|
|
|
|
|
|
|
|
if (!uv_data) {
|
|
|
|
|
WM_reportf(RPT_WARNING,
|
|
|
|
|
"USD Import: couldn't add UV attribute '%s'",
|
|
|
|
|
primvar.GetBaseName().GetText());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-12 18:48:20 +10:00
|
|
|
if (ELEM(varying_type, pxr::UsdGeomTokens->faceVarying, pxr::UsdGeomTokens->varying)) {
|
2023-08-11 23:47:17 +02:00
|
|
|
if (is_left_handed_) {
|
|
|
|
|
/* Reverse the index order. */
|
|
|
|
|
const OffsetIndices faces = mesh->faces();
|
|
|
|
|
for (const int i : faces.index_range()) {
|
|
|
|
|
const IndexRange face = faces[i];
|
|
|
|
|
for (int j : face.index_range()) {
|
|
|
|
|
const int rev_index = face.last(j);
|
|
|
|
|
uv_data.span[face.start() + j] = float2(usd_uvs[rev_index][0], usd_uvs[rev_index][1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (int i = 0; i < uv_data.span.size(); ++i) {
|
|
|
|
|
uv_data.span[i] = float2(usd_uvs[i][0], usd_uvs[i][1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Handle vertex interpolation. */
|
|
|
|
|
const Span<int> corner_verts = mesh->corner_verts();
|
|
|
|
|
BLI_assert(mesh->totvert == usd_uvs.size());
|
|
|
|
|
for (int i = 0; i < uv_data.span.size(); ++i) {
|
|
|
|
|
/* Get the vertex index for this corner. */
|
|
|
|
|
int vi = corner_verts[i];
|
|
|
|
|
uv_data.span[i] = float2(usd_uvs[vi][0], usd_uvs[vi][1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uv_data.finish();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename USDT, typename BlenderT> inline BlenderT convert_value(const USDT &value)
|
|
|
|
|
{
|
|
|
|
|
/* Default is no conversion. */
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<> inline float2 convert_value(const pxr::GfVec2f &value)
|
|
|
|
|
{
|
|
|
|
|
return float2(value[0], value[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<> inline float3 convert_value(const pxr::GfVec3f &value)
|
|
|
|
|
{
|
|
|
|
|
return float3(value[0], value[1], value[2]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<> inline ColorGeometry4f convert_value(const pxr::GfVec3f &value)
|
|
|
|
|
{
|
|
|
|
|
return ColorGeometry4f(value[0], value[1], value[2], 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename USDT, typename BlenderT>
|
|
|
|
|
void USDMeshReader::copy_prim_array_to_blender_attribute(const Mesh *mesh,
|
|
|
|
|
const pxr::UsdGeomPrimvar &primvar,
|
|
|
|
|
const double motionSampleTime,
|
|
|
|
|
MutableSpan<BlenderT> attribute)
|
|
|
|
|
{
|
|
|
|
|
const pxr::TfToken interp = primvar.GetInterpolation();
|
|
|
|
|
pxr::VtArray<USDT> primvar_array = get_prim_attribute_array<USDT>(primvar, motionSampleTime);
|
|
|
|
|
if (primvar_array.empty()) {
|
|
|
|
|
WM_reportf(
|
|
|
|
|
RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (interp == pxr::UsdGeomTokens->constant) {
|
|
|
|
|
/* For situations where there's only a single item, flood fill the object. */
|
|
|
|
|
attribute.fill(convert_value<USDT, BlenderT>(primvar_array[0]));
|
|
|
|
|
}
|
|
|
|
|
else if (interp == pxr::UsdGeomTokens->faceVarying) {
|
|
|
|
|
if (is_left_handed_) {
|
|
|
|
|
/* Reverse the index order. */
|
|
|
|
|
const OffsetIndices faces = mesh->faces();
|
|
|
|
|
for (const int i : faces.index_range()) {
|
|
|
|
|
const IndexRange face = faces[i];
|
|
|
|
|
for (int j : face.index_range()) {
|
|
|
|
|
const int rev_index = face.last(j);
|
|
|
|
|
attribute[face.start() + j] = convert_value<USDT, BlenderT>(primvar_array[rev_index]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (const int64_t i : attribute.index_range()) {
|
|
|
|
|
attribute[i] = convert_value<USDT, BlenderT>(primvar_array[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
/* Assume direct one-to-one mapping. */
|
|
|
|
|
if (primvar_array.size() == attribute.size()) {
|
|
|
|
|
if constexpr (std::is_same_v<USDT, BlenderT>) {
|
|
|
|
|
const Span<USDT> src(primvar_array.data(), primvar_array.size());
|
|
|
|
|
attribute.copy_from(src);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (const int64_t i : attribute.index_range()) {
|
|
|
|
|
attribute[i] = convert_value<USDT, BlenderT>(primvar_array[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::read_generic_data_primvar(Mesh *mesh,
|
|
|
|
|
const pxr::UsdGeomPrimvar &primvar,
|
|
|
|
|
const double motionSampleTime)
|
|
|
|
|
{
|
|
|
|
|
const pxr::SdfValueTypeName sdf_type = primvar.GetTypeName();
|
|
|
|
|
const pxr::TfToken varying_type = primvar.GetInterpolation();
|
|
|
|
|
const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName());
|
|
|
|
|
|
|
|
|
|
const std::optional<eAttrDomain> domain = convert_usd_varying_to_blender(varying_type);
|
|
|
|
|
const std::optional<eCustomDataType> type = convert_usd_type_to_blender(sdf_type);
|
|
|
|
|
|
|
|
|
|
if (!domain.has_value() || !type.has_value()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
|
|
|
bke::GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span(
|
|
|
|
|
name.GetText(), *domain, *type);
|
|
|
|
|
switch (*type) {
|
|
|
|
|
case CD_PROP_FLOAT:
|
|
|
|
|
copy_prim_array_to_blender_attribute<float>(
|
|
|
|
|
mesh, primvar, motionSampleTime, attribute.span.typed<float>());
|
|
|
|
|
break;
|
|
|
|
|
case CD_PROP_INT32:
|
|
|
|
|
copy_prim_array_to_blender_attribute<int32_t>(
|
|
|
|
|
mesh, primvar, motionSampleTime, attribute.span.typed<int>());
|
|
|
|
|
break;
|
|
|
|
|
case CD_PROP_FLOAT2:
|
|
|
|
|
copy_prim_array_to_blender_attribute<pxr::GfVec2f>(
|
|
|
|
|
mesh, primvar, motionSampleTime, attribute.span.typed<float2>());
|
|
|
|
|
break;
|
|
|
|
|
case CD_PROP_FLOAT3:
|
|
|
|
|
copy_prim_array_to_blender_attribute<pxr::GfVec3f>(
|
|
|
|
|
mesh, primvar, motionSampleTime, attribute.span.typed<float3>());
|
|
|
|
|
break;
|
|
|
|
|
case CD_PROP_COLOR:
|
|
|
|
|
copy_prim_array_to_blender_attribute<pxr::GfVec3f>(
|
|
|
|
|
mesh, primvar, motionSampleTime, attribute.span.typed<ColorGeometry4f>());
|
|
|
|
|
break;
|
|
|
|
|
case CD_PROP_BOOL:
|
|
|
|
|
copy_prim_array_to_blender_attribute<bool>(
|
|
|
|
|
mesh, primvar, motionSampleTime, attribute.span.typed<bool>());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
WM_reportf(RPT_ERROR,
|
|
|
|
|
"Generic primvar %s: invalid type %s",
|
|
|
|
|
primvar.GetName().GetText(),
|
|
|
|
|
sdf_type.GetAsToken().GetText());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
attribute.finish();
|
|
|
|
|
}
|
|
|
|
|
|
Subdivision: add support for vertex creasing
This adds vertex creasing support for OpenSubDiv for modeling, rendering,
Alembic and USD I/O.
For modeling, vertex creasing follows the edge creasing implementation with an
operator accessible through the Vertex menu in Edit Mode, and some parameter in
the properties panel. The option in the Subsurf and Multires to use edge
creasing also affects vertex creasing.
The vertex crease data is stored as a CustomData layer, unlike edge creases
which for now are stored in `MEdge`, but will in the future also be moved to
a `CustomData` layer. See comments for details on the difference in behavior
for the `CD_CREASE` layer between egdes and vertices.
For Cycles this adds sockets on the Mesh node to hold data about which vertices
are creased (one socket for the indices, one for the weigths).
Viewport rendering of vertex creasing reuses the same color scheme as for edges
and creased vertices are drawn bigger than uncreased vertices.
For Alembic and USD, vertex crease support follows the edge crease
implementation, they are always read, but only exported if a `Subsurf` modifier
is present on the Mesh.
Reviewed By: brecht, fclem, sergey, sybren, campbellbarton
Differential Revision: https://developer.blender.org/D10145
2022-01-20 12:20:30 +01:00
|
|
|
void USDMeshReader::read_vertex_creases(Mesh *mesh, const double motionSampleTime)
|
|
|
|
|
{
|
|
|
|
|
pxr::VtIntArray corner_indices;
|
|
|
|
|
if (!mesh_prim_.GetCornerIndicesAttr().Get(&corner_indices, motionSampleTime)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxr::VtIntArray corner_sharpnesses;
|
|
|
|
|
if (!mesh_prim_.GetCornerSharpnessesAttr().Get(&corner_sharpnesses, motionSampleTime)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* It is fine to have fewer indices than vertices, but never the other way other. */
|
|
|
|
|
if (corner_indices.size() > mesh->totvert) {
|
|
|
|
|
std::cerr << "WARNING: too many vertex crease for mesh " << prim_path_ << std::endl;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (corner_indices.size() != corner_sharpnesses.size()) {
|
|
|
|
|
std::cerr << "WARNING: vertex crease indices and sharpnesses count mismatch for mesh "
|
|
|
|
|
<< prim_path_ << std::endl;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-13 20:23:39 +02:00
|
|
|
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
|
|
|
|
bke::SpanAttributeWriter creases = attributes.lookup_or_add_for_write_span<float>(
|
|
|
|
|
"crease_vert", ATTR_DOMAIN_POINT);
|
Subdivision: add support for vertex creasing
This adds vertex creasing support for OpenSubDiv for modeling, rendering,
Alembic and USD I/O.
For modeling, vertex creasing follows the edge creasing implementation with an
operator accessible through the Vertex menu in Edit Mode, and some parameter in
the properties panel. The option in the Subsurf and Multires to use edge
creasing also affects vertex creasing.
The vertex crease data is stored as a CustomData layer, unlike edge creases
which for now are stored in `MEdge`, but will in the future also be moved to
a `CustomData` layer. See comments for details on the difference in behavior
for the `CD_CREASE` layer between egdes and vertices.
For Cycles this adds sockets on the Mesh node to hold data about which vertices
are creased (one socket for the indices, one for the weigths).
Viewport rendering of vertex creasing reuses the same color scheme as for edges
and creased vertices are drawn bigger than uncreased vertices.
For Alembic and USD, vertex crease support follows the edge crease
implementation, they are always read, but only exported if a `Subsurf` modifier
is present on the Mesh.
Reviewed By: brecht, fclem, sergey, sybren, campbellbarton
Differential Revision: https://developer.blender.org/D10145
2022-01-20 12:20:30 +01:00
|
|
|
|
|
|
|
|
for (size_t i = 0; i < corner_indices.size(); i++) {
|
2023-06-13 20:23:39 +02:00
|
|
|
creases.span[corner_indices[i]] = corner_sharpnesses[i];
|
Subdivision: add support for vertex creasing
This adds vertex creasing support for OpenSubDiv for modeling, rendering,
Alembic and USD I/O.
For modeling, vertex creasing follows the edge creasing implementation with an
operator accessible through the Vertex menu in Edit Mode, and some parameter in
the properties panel. The option in the Subsurf and Multires to use edge
creasing also affects vertex creasing.
The vertex crease data is stored as a CustomData layer, unlike edge creases
which for now are stored in `MEdge`, but will in the future also be moved to
a `CustomData` layer. See comments for details on the difference in behavior
for the `CD_CREASE` layer between egdes and vertices.
For Cycles this adds sockets on the Mesh node to hold data about which vertices
are creased (one socket for the indices, one for the weigths).
Viewport rendering of vertex creasing reuses the same color scheme as for edges
and creased vertices are drawn bigger than uncreased vertices.
For Alembic and USD, vertex crease support follows the edge crease
implementation, they are always read, but only exported if a `Subsurf` modifier
is present on the Mesh.
Reviewed By: brecht, fclem, sergey, sybren, campbellbarton
Differential Revision: https://developer.blender.org/D10145
2022-01-20 12:20:30 +01:00
|
|
|
}
|
2023-06-13 20:23:39 +02:00
|
|
|
creases.finish();
|
Subdivision: add support for vertex creasing
This adds vertex creasing support for OpenSubDiv for modeling, rendering,
Alembic and USD I/O.
For modeling, vertex creasing follows the edge creasing implementation with an
operator accessible through the Vertex menu in Edit Mode, and some parameter in
the properties panel. The option in the Subsurf and Multires to use edge
creasing also affects vertex creasing.
The vertex crease data is stored as a CustomData layer, unlike edge creases
which for now are stored in `MEdge`, but will in the future also be moved to
a `CustomData` layer. See comments for details on the difference in behavior
for the `CD_CREASE` layer between egdes and vertices.
For Cycles this adds sockets on the Mesh node to hold data about which vertices
are creased (one socket for the indices, one for the weigths).
Viewport rendering of vertex creasing reuses the same color scheme as for edges
and creased vertices are drawn bigger than uncreased vertices.
For Alembic and USD, vertex crease support follows the edge crease
implementation, they are always read, but only exported if a `Subsurf` modifier
is present on the Mesh.
Reviewed By: brecht, fclem, sergey, sybren, campbellbarton
Differential Revision: https://developer.blender.org/D10145
2022-01-20 12:20:30 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-03 11:55:53 +02:00
|
|
|
void USDMeshReader::process_normals_vertex_varying(Mesh *mesh)
|
|
|
|
|
{
|
|
|
|
|
if (!mesh) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (normals_.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (normals_.size() != mesh->totvert) {
|
|
|
|
|
std::cerr << "WARNING: vertex varying normals count mismatch for mesh " << prim_path_
|
|
|
|
|
<< std::endl;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Refactor: Move normals out of MVert, lazy calculation
As described in T91186, this commit moves mesh vertex normals into a
contiguous array of float vectors in a custom data layer, how face
normals are currently stored.
The main interface is documented in `BKE_mesh.h`. Vertex and face
normals are now calculated on-demand and cached, retrieved with an
"ensure" function. Since the logical state of a mesh is now "has
normals when necessary", they can be retrieved from a `const` mesh.
The goal is to use on-demand calculation for all derived data, but
leave room for eager calculation for performance purposes (modifier
evaluation is threaded, but viewport data generation is not).
**Benefits**
This moves us closer to a SoA approach rather than the current AoS
paradigm. Accessing a contiguous `float3` is much more efficient than
retrieving data from a larger struct. The memory requirements for
accessing only normals or vertex locations are smaller, and at the
cost of more memory usage for just normals, they now don't have to
be converted between float and short, which also simplifies code
In the future, the remaining items can be removed from `MVert`,
leaving only `float3`, which has similar benefits (see T93602).
Removing the combination of derived and original data makes it
conceptually simpler to only calculate normals when necessary.
This is especially important now that we have more opportunities
for temporary meshes in geometry nodes.
**Performance**
In addition to the theoretical future performance improvements by
making `MVert == float3`, I've done some basic performance testing
on this patch directly. The data is fairly rough, but it gives an idea
about where things stand generally.
- Mesh line primitive 4m Verts: 1.16x faster (36 -> 31 ms),
showing that accessing just `MVert` is now more efficient.
- Spring Splash Screen: 1.03-1.06 -> 1.06-1.11 FPS, a very slight
change that at least shows there is no regression.
- Sprite Fright Snail Smoosh: 3.30-3.40 -> 3.42-3.50 FPS, a small
but observable speedup.
- Set Position Node with Scaled Normal: 1.36x faster (53 -> 39 ms),
shows that using normals in geometry nodes is faster.
- Normal Calculation 1.6m Vert Cube: 1.19x faster (25 -> 21 ms),
shows that calculating normals is slightly faster now.
- File Size of 1.6m Vert Cube: 1.03x smaller (214.7 -> 208.4 MB),
Normals are not saved in files, which can help with large meshes.
As for memory usage, it may be slightly more in some cases, but
I didn't observe any difference in the production files I tested.
**Tests**
Some modifiers and cycles test results need to be updated with this
commit, for two reasons:
- The subdivision surface modifier is not responsible for calculating
normals anymore. In master, the modifier creates different normals
than the result of the `Mesh` normal calculation, so this is a bug
fix.
- There are small differences in the results of some modifiers that
use normals because they are not converted to and from `short`
anymore.
**Future improvements**
- Remove `ModifierTypeInfo::dependsOnNormals`. Code in each modifier
already retrieves normals if they are needed anyway.
- Copy normals as part of a better CoW system for attributes.
- Make more areas use lazy instead of eager normal calculation.
- Remove `BKE_mesh_normals_tag_dirty` in more places since that is
now the default state of a new mesh.
- Possibly apply a similar change to derived face corner normals.
Differential Revision: https://developer.blender.org/D12770
2022-01-13 14:37:58 -06:00
|
|
|
BLI_STATIC_ASSERT(sizeof(normals_[0]) == sizeof(float3), "Expected float3 normals size");
|
2023-08-03 16:13:04 +02:00
|
|
|
bke::mesh_vert_normals_assign(
|
|
|
|
|
*mesh, Span(reinterpret_cast<const float3 *>(normals_.data()), int64_t(normals_.size())));
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::process_normals_face_varying(Mesh *mesh)
|
|
|
|
|
{
|
|
|
|
|
if (normals_.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for normals count mismatches to prevent crashes. */
|
|
|
|
|
if (normals_.size() != mesh->totloop) {
|
|
|
|
|
std::cerr << "WARNING: loop normal count mismatch for mesh " << mesh->id.name << std::endl;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mesh->flag |= ME_AUTOSMOOTH;
|
|
|
|
|
|
|
|
|
|
long int loop_count = normals_.size();
|
|
|
|
|
|
|
|
|
|
float(*lnors)[3] = static_cast<float(*)[3]>(
|
|
|
|
|
MEM_malloc_arrayN(loop_count, sizeof(float[3]), "USD::FaceNormals"));
|
|
|
|
|
|
2023-07-24 22:06:55 +02:00
|
|
|
const OffsetIndices faces = mesh->faces();
|
|
|
|
|
for (const int i : faces.index_range()) {
|
|
|
|
|
const IndexRange face = faces[i];
|
2023-08-11 23:47:17 +02:00
|
|
|
for (int j : face.index_range()) {
|
2023-07-24 22:06:55 +02:00
|
|
|
int blender_index = face.start() + j;
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-07-24 22:06:55 +02:00
|
|
|
int usd_index = face.start();
|
2021-08-03 11:55:53 +02:00
|
|
|
if (is_left_handed_) {
|
2023-07-24 22:06:55 +02:00
|
|
|
usd_index += face.size() - 1 - j;
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
usd_index += j;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lnors[blender_index][0] = normals_[usd_index][0];
|
|
|
|
|
lnors[blender_index][1] = normals_[usd_index][1];
|
|
|
|
|
lnors[blender_index][2] = normals_[usd_index][2];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BKE_mesh_set_custom_normals(mesh, lnors);
|
|
|
|
|
|
|
|
|
|
MEM_freeN(lnors);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::process_normals_uniform(Mesh *mesh)
|
|
|
|
|
{
|
|
|
|
|
if (normals_.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for normals count mismatches to prevent crashes. */
|
2023-07-24 22:06:55 +02:00
|
|
|
if (normals_.size() != mesh->faces_num) {
|
2021-08-03 11:55:53 +02:00
|
|
|
std::cerr << "WARNING: uniform normal count mismatch for mesh " << mesh->id.name << std::endl;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float(*lnors)[3] = static_cast<float(*)[3]>(
|
|
|
|
|
MEM_malloc_arrayN(mesh->totloop, sizeof(float[3]), "USD::FaceNormals"));
|
|
|
|
|
|
2023-07-24 22:06:55 +02:00
|
|
|
const OffsetIndices faces = mesh->faces();
|
|
|
|
|
for (const int i : faces.index_range()) {
|
|
|
|
|
for (const int corner : faces[i]) {
|
Mesh: Replace MPoly struct with offset indices
Implements #95967.
Currently the `MPoly` struct is 12 bytes, and stores the index of a
face's first corner and the number of corners/verts/edges. Polygons
and corners are always created in order by Blender, meaning each
face's corners will be after the previous face's corners. We can take
advantage of this fact and eliminate the redundancy in mesh face
storage by only storing a single integer corner offset for each face.
The size of the face is then encoded by the offset of the next face.
The size of a single integer is 4 bytes, so this reduces memory
usage by 3 times.
The same method is used for `CurvesGeometry`, so Blender already has
an abstraction to simplify using these offsets called `OffsetIndices`.
This class is used to easily retrieve a range of corner indices for
each face. This also gives the opportunity for sharing some logic with
curves.
Another benefit of the change is that the offsets and sizes stored in
`MPoly` can no longer disagree with each other. Storing faces in the
order of their corners can simplify some code too.
Face/polygon variables now use the `IndexRange` type, which comes with
quite a few utilities that can simplify code.
Some:
- The offset integer array has to be one longer than the face count to
avoid a branch for every face, which means the data is no longer part
of the mesh's `CustomData`.
- We lose the ability to "reference" an original mesh's offset array
until more reusable CoW from #104478 is committed. That will be added
in a separate commit.
- Since they aren't part of `CustomData`, poly offsets often have to be
copied manually.
- To simplify using `OffsetIndices` in many places, some functions and
structs in headers were moved to only compile in C++.
- All meshes created by Blender use the same order for faces and face
corners, but just in case, meshes with mismatched order are fixed by
versioning code.
- `MeshPolygon.totloop` is no longer editable in RNA. This API break is
necessary here unfortunately. It should be worth it in 3.6, since
that's the best way to allow loading meshes from 4.0, which is
important for an LTS version.
Pull Request: https://projects.blender.org/blender/blender/pulls/105938
2023-04-04 20:39:28 +02:00
|
|
|
lnors[corner][0] = normals_[i][0];
|
|
|
|
|
lnors[corner][1] = normals_[i][1];
|
|
|
|
|
lnors[corner][2] = normals_[i][2];
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mesh->flag |= ME_AUTOSMOOTH;
|
|
|
|
|
BKE_mesh_set_custom_normals(mesh, lnors);
|
|
|
|
|
|
|
|
|
|
MEM_freeN(lnors);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::read_mesh_sample(ImportSettings *settings,
|
|
|
|
|
Mesh *mesh,
|
|
|
|
|
const double motionSampleTime,
|
|
|
|
|
const bool new_mesh)
|
|
|
|
|
{
|
2023-07-24 22:06:55 +02:00
|
|
|
/* Note that for new meshes we always want to read verts and faces,
|
2021-08-04 10:03:07 +10:00
|
|
|
* regardless of the value of the read_flag, to avoid a crash downstream
|
2021-08-03 11:55:53 +02:00
|
|
|
* in code that expect this data to be there. */
|
|
|
|
|
|
|
|
|
|
if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) {
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 00:10:43 -05:00
|
|
|
MutableSpan<float3> vert_positions = mesh->vert_positions_for_write();
|
2021-08-03 11:55:53 +02:00
|
|
|
for (int i = 0; i < positions_.size(); i++) {
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 00:10:43 -05:00
|
|
|
vert_positions[i] = {positions_[i][0], positions_[i][1], positions_[i][2]};
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
2023-02-27 16:08:48 -05:00
|
|
|
BKE_mesh_tag_positions_changed(mesh);
|
Subdivision: add support for vertex creasing
This adds vertex creasing support for OpenSubDiv for modeling, rendering,
Alembic and USD I/O.
For modeling, vertex creasing follows the edge creasing implementation with an
operator accessible through the Vertex menu in Edit Mode, and some parameter in
the properties panel. The option in the Subsurf and Multires to use edge
creasing also affects vertex creasing.
The vertex crease data is stored as a CustomData layer, unlike edge creases
which for now are stored in `MEdge`, but will in the future also be moved to
a `CustomData` layer. See comments for details on the difference in behavior
for the `CD_CREASE` layer between egdes and vertices.
For Cycles this adds sockets on the Mesh node to hold data about which vertices
are creased (one socket for the indices, one for the weigths).
Viewport rendering of vertex creasing reuses the same color scheme as for edges
and creased vertices are drawn bigger than uncreased vertices.
For Alembic and USD, vertex crease support follows the edge crease
implementation, they are always read, but only exported if a `Subsurf` modifier
is present on the Mesh.
Reviewed By: brecht, fclem, sergey, sybren, campbellbarton
Differential Revision: https://developer.blender.org/D10145
2022-01-20 12:20:30 +01:00
|
|
|
|
|
|
|
|
read_vertex_creases(mesh, motionSampleTime);
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) {
|
|
|
|
|
read_mpolys(mesh);
|
|
|
|
|
if (normal_interpolation_ == pxr::UsdGeomTokens->faceVarying) {
|
|
|
|
|
process_normals_face_varying(mesh);
|
|
|
|
|
}
|
|
|
|
|
else if (normal_interpolation_ == pxr::UsdGeomTokens->uniform) {
|
|
|
|
|
process_normals_uniform(mesh);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-24 22:06:55 +02:00
|
|
|
/* Process point normals after reading faces. */
|
2021-08-03 11:55:53 +02:00
|
|
|
if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0 &&
|
|
|
|
|
normal_interpolation_ == pxr::UsdGeomTokens->vertex)
|
|
|
|
|
{
|
|
|
|
|
process_normals_vertex_varying(mesh);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-14 11:05:26 +02:00
|
|
|
/* Custom Data layers. */
|
2023-08-11 23:47:17 +02:00
|
|
|
if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) ||
|
|
|
|
|
(settings->read_flag & MOD_MESHSEQ_READ_COLOR) ||
|
|
|
|
|
(settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES))
|
|
|
|
|
{
|
|
|
|
|
read_custom_data(settings, mesh, motionSampleTime, new_mesh);
|
|
|
|
|
}
|
2023-04-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::read_custom_data(const ImportSettings *settings,
|
|
|
|
|
Mesh *mesh,
|
2023-08-11 23:47:17 +02:00
|
|
|
const double motionSampleTime,
|
|
|
|
|
const bool new_mesh)
|
2023-04-14 11:05:26 +02:00
|
|
|
{
|
2023-08-11 23:47:17 +02:00
|
|
|
if (!(mesh && mesh_prim_ && mesh->totloop > 0)) {
|
|
|
|
|
return;
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
2023-04-14 11:05:26 +02:00
|
|
|
|
2023-08-11 23:47:17 +02:00
|
|
|
pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(mesh_prim_);
|
|
|
|
|
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
|
|
|
|
|
|
|
|
|
|
pxr::TfToken active_color_name;
|
|
|
|
|
pxr::TfToken active_uv_set_name;
|
|
|
|
|
|
|
|
|
|
/* Convert primvars to custom layer data. */
|
|
|
|
|
for (pxr::UsdGeomPrimvar &pv : primvars) {
|
|
|
|
|
if (!pv.HasValue()) {
|
|
|
|
|
WM_reportf(RPT_WARNING,
|
|
|
|
|
"Skipping primvar %s, mesh %s -- no value",
|
|
|
|
|
pv.GetName().GetText(),
|
|
|
|
|
&mesh->id.name[2]);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pxr::SdfValueTypeName type = pv.GetTypeName();
|
|
|
|
|
const pxr::TfToken varying_type = pv.GetInterpolation();
|
|
|
|
|
const pxr::TfToken name = pv.StripPrimvarsName(pv.GetPrimvarName());
|
|
|
|
|
|
|
|
|
|
/* To avoid unnecessarily reloading static primvars during animation,
|
|
|
|
|
* early out if not first load and this primvar isn't animated. */
|
|
|
|
|
if (!new_mesh && primvar_varying_map_.find(name) != primvar_varying_map_.end() &&
|
|
|
|
|
!primvar_varying_map_.at(name))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Read Color primvars. */
|
|
|
|
|
if (convert_usd_type_to_blender(type) == CD_PROP_COLOR) {
|
|
|
|
|
if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) {
|
|
|
|
|
/* Set the active color name to 'displayColor', if a color primvar
|
|
|
|
|
* with this name exists. Otherwise, use the name of the first
|
|
|
|
|
* color primvar we find for the active color. */
|
|
|
|
|
if (active_color_name.IsEmpty() || name == usdtokens::displayColor) {
|
|
|
|
|
active_color_name = name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
read_color_data_primvar(mesh, pv, motionSampleTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Read UV primvars. */
|
|
|
|
|
else if (ELEM(varying_type,
|
|
|
|
|
pxr::UsdGeomTokens->vertex,
|
|
|
|
|
pxr::UsdGeomTokens->faceVarying,
|
|
|
|
|
pxr::UsdGeomTokens->varying) &&
|
|
|
|
|
convert_usd_type_to_blender(type) == CD_PROP_FLOAT2)
|
|
|
|
|
{
|
|
|
|
|
if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
|
|
|
|
|
/* Set the active uv set name to 'st', if a uv set primvar
|
|
|
|
|
* with this name exists. Otherwise, use the name of the first
|
|
|
|
|
* uv set primvar we find for the active uv set. */
|
|
|
|
|
if (active_uv_set_name.IsEmpty() || name == usdtokens::st) {
|
|
|
|
|
active_uv_set_name = name;
|
|
|
|
|
}
|
|
|
|
|
read_uv_data_primvar(mesh, pv, motionSampleTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Read all other primvars. */
|
|
|
|
|
else {
|
|
|
|
|
if ((settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES) != 0) {
|
|
|
|
|
read_generic_data_primvar(mesh, pv, motionSampleTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Record whether the primvar attribute might be time varying. */
|
|
|
|
|
if (primvar_varying_map_.find(name) == primvar_varying_map_.end()) {
|
|
|
|
|
bool might_be_time_varying = pv.ValueMightBeTimeVarying();
|
|
|
|
|
primvar_varying_map_.insert(std::make_pair(name, might_be_time_varying));
|
|
|
|
|
if (might_be_time_varying) {
|
|
|
|
|
is_time_varying_ = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} /* End primvar attribute loop. */
|
|
|
|
|
|
|
|
|
|
if (!active_color_name.IsEmpty()) {
|
|
|
|
|
BKE_id_attributes_default_color_set(&mesh->id, active_color_name.GetText());
|
|
|
|
|
BKE_id_attributes_active_color_set(&mesh->id, active_color_name.GetText());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!active_uv_set_name.IsEmpty()) {
|
|
|
|
|
int layer_index = CustomData_get_named_layer_index(
|
|
|
|
|
&mesh->loop_data, CD_PROP_FLOAT2, active_uv_set_name.GetText());
|
|
|
|
|
if (layer_index > -1) {
|
|
|
|
|
CustomData_set_layer_active_index(&mesh->loop_data, CD_PROP_FLOAT2, layer_index);
|
|
|
|
|
CustomData_set_layer_render_index(&mesh->loop_data, CD_PROP_FLOAT2, layer_index);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-31 09:09:01 -05:00
|
|
|
void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime,
|
|
|
|
|
MutableSpan<int> material_indices,
|
|
|
|
|
std::map<pxr::SdfPath, int> *r_mat_map)
|
2021-08-03 11:55:53 +02:00
|
|
|
{
|
|
|
|
|
if (r_mat_map == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find the geom subsets that have bound materials.
|
2021-08-04 10:03:07 +10:00
|
|
|
* We don't call #pxr::UsdShadeMaterialBindingAPI::GetMaterialBindSubsets()
|
2021-08-03 11:55:53 +02:00
|
|
|
* because this function returns only those subsets that are in the 'materialBind'
|
|
|
|
|
* family, but, in practice, applications (like Houdini) might export subsets
|
|
|
|
|
* in different families that are bound to materials.
|
|
|
|
|
* TODO(makowalski): Reassess if the above is the best approach. */
|
|
|
|
|
const std::vector<pxr::UsdGeomSubset> subsets = pxr::UsdGeomSubset::GetAllGeomSubsets(
|
|
|
|
|
mesh_prim_);
|
|
|
|
|
|
|
|
|
|
int current_mat = 0;
|
|
|
|
|
if (!subsets.empty()) {
|
|
|
|
|
for (const pxr::UsdGeomSubset &subset : subsets) {
|
|
|
|
|
|
2022-06-10 10:12:41 -04:00
|
|
|
pxr::UsdShadeMaterial subset_mtl = utils::compute_bound_material(subset.GetPrim());
|
2021-08-03 11:55:53 +02:00
|
|
|
if (!subset_mtl) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxr::SdfPath subset_mtl_path = subset_mtl.GetPath();
|
|
|
|
|
|
|
|
|
|
if (subset_mtl_path.IsEmpty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (r_mat_map->find(subset_mtl_path) == r_mat_map->end()) {
|
|
|
|
|
(*r_mat_map)[subset_mtl_path] = 1 + current_mat++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int mat_idx = (*r_mat_map)[subset_mtl_path] - 1;
|
|
|
|
|
|
|
|
|
|
pxr::UsdAttribute indicesAttribute = subset.GetIndicesAttr();
|
|
|
|
|
pxr::VtIntArray indices;
|
|
|
|
|
indicesAttribute.Get(&indices, motionSampleTime);
|
|
|
|
|
|
2022-08-31 09:09:01 -05:00
|
|
|
for (const int i : indices) {
|
|
|
|
|
material_indices[i] = mat_idx;
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (r_mat_map->empty()) {
|
|
|
|
|
|
2022-06-10 10:12:41 -04:00
|
|
|
pxr::UsdShadeMaterial mtl = utils::compute_bound_material(prim_);
|
|
|
|
|
if (mtl) {
|
2021-08-03 11:55:53 +02:00
|
|
|
pxr::SdfPath mtl_path = mtl.GetPath();
|
|
|
|
|
|
|
|
|
|
if (!mtl_path.IsEmpty()) {
|
|
|
|
|
r_mat_map->insert(std::make_pair(mtl.GetPath(), 1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double motionSampleTime)
|
|
|
|
|
{
|
|
|
|
|
if (!import_params_.import_materials) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::map<pxr::SdfPath, int> mat_map;
|
2022-08-31 09:09:01 -05:00
|
|
|
|
2022-09-07 21:41:39 -05:00
|
|
|
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
2022-10-20 17:23:37 -05:00
|
|
|
bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
|
|
|
|
|
"material_index", ATTR_DOMAIN_FACE);
|
2022-08-31 09:09:01 -05:00
|
|
|
this->assign_facesets_to_material_indices(motionSampleTime, material_indices.span, &mat_map);
|
|
|
|
|
material_indices.finish();
|
2022-06-17 22:28:22 +03:00
|
|
|
/* Build material name map if it's not built yet. */
|
|
|
|
|
if (this->settings_->mat_name_to_mat.empty()) {
|
2023-01-25 10:46:07 -05:00
|
|
|
build_material_map(bmain, &this->settings_->mat_name_to_mat);
|
2022-06-17 22:28:22 +03:00
|
|
|
}
|
2022-06-10 10:12:41 -04:00
|
|
|
utils::assign_materials(bmain,
|
|
|
|
|
object_,
|
|
|
|
|
mat_map,
|
|
|
|
|
this->import_params_,
|
|
|
|
|
this->prim_.GetStage(),
|
2022-06-17 22:28:22 +03:00
|
|
|
this->settings_->mat_name_to_mat,
|
2022-06-10 10:12:41 -04:00
|
|
|
this->settings_->usd_path_to_mat_name);
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
|
2023-02-22 11:22:48 -05:00
|
|
|
const USDMeshReadParams params,
|
2023-09-22 12:02:32 +10:00
|
|
|
const char ** /*err_str*/)
|
2021-08-03 11:55:53 +02:00
|
|
|
{
|
|
|
|
|
if (!mesh_prim_) {
|
|
|
|
|
return existing_mesh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mesh_prim_.GetOrientationAttr().Get(&orientation_);
|
|
|
|
|
if (orientation_ == pxr::UsdGeomTokens->leftHanded) {
|
|
|
|
|
is_left_handed_ = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Mesh *active_mesh = existing_mesh;
|
|
|
|
|
bool new_mesh = false;
|
|
|
|
|
|
2021-08-04 10:03:07 +10:00
|
|
|
/* TODO(makowalski): implement the optimization of only updating the mesh points when
|
2021-08-03 11:55:53 +02:00
|
|
|
* the topology is consistent, as in the Alembic importer. */
|
|
|
|
|
|
|
|
|
|
ImportSettings settings;
|
2023-02-22 11:22:48 -05:00
|
|
|
settings.read_flag |= params.read_flags;
|
2021-08-03 11:55:53 +02:00
|
|
|
|
2023-02-22 11:22:48 -05:00
|
|
|
if (topology_changed(existing_mesh, params.motion_sample_time)) {
|
2021-08-03 11:55:53 +02:00
|
|
|
new_mesh = true;
|
|
|
|
|
active_mesh = BKE_mesh_new_nomain_from_template(
|
2023-04-19 15:28:53 -04:00
|
|
|
existing_mesh, positions_.size(), 0, face_counts_.size(), face_indices_.size());
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-26 11:55:22 +13:00
|
|
|
read_mesh_sample(
|
|
|
|
|
&settings, active_mesh, params.motion_sample_time, new_mesh || is_initial_load_);
|
2021-08-03 11:55:53 +02:00
|
|
|
|
|
|
|
|
if (new_mesh) {
|
|
|
|
|
/* Here we assume that the number of materials doesn't change, i.e. that
|
|
|
|
|
* the material slots that were created when the object was loaded from
|
|
|
|
|
* USD are still valid now. */
|
2023-07-24 22:06:55 +02:00
|
|
|
if (active_mesh->faces_num != 0 && import_params_.import_materials) {
|
2021-08-03 11:55:53 +02:00
|
|
|
std::map<pxr::SdfPath, int> mat_map;
|
2022-09-07 21:41:39 -05:00
|
|
|
bke::MutableAttributeAccessor attributes = active_mesh->attributes_for_write();
|
2022-08-31 09:09:01 -05:00
|
|
|
bke::SpanAttributeWriter<int> material_indices =
|
2022-10-20 17:23:37 -05:00
|
|
|
attributes.lookup_or_add_for_write_span<int>("material_index", ATTR_DOMAIN_FACE);
|
2023-02-26 11:55:22 +13:00
|
|
|
assign_facesets_to_material_indices(
|
|
|
|
|
params.motion_sample_time, material_indices.span, &mat_map);
|
2022-08-31 09:09:01 -05:00
|
|
|
material_indices.finish();
|
2021-08-03 11:55:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return active_mesh;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-17 20:11:51 +02:00
|
|
|
std::string USDMeshReader::get_skeleton_path() const
|
|
|
|
|
{
|
|
|
|
|
/* Make sure we can apply UsdSkelBindingAPI to the prim.
|
|
|
|
|
* Attempting to apply the API to instance proxies generates
|
|
|
|
|
* a USD error. */
|
|
|
|
|
if (!prim_ || prim_.IsInstanceProxy()) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_);
|
|
|
|
|
|
|
|
|
|
if (!skel_api) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton()) {
|
|
|
|
|
return skel.GetPath().GetAsString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<XformResult> USDMeshReader::get_local_usd_xform(const float time) const
|
|
|
|
|
{
|
|
|
|
|
if (!import_params_.import_skeletons || prim_.IsInstanceProxy()) {
|
|
|
|
|
/* Use the standard transform computation, since we are ignoring
|
|
|
|
|
* skinning data. Note that applying the UsdSkelBinding API to an
|
|
|
|
|
* instance proxy generates a USD error. */
|
|
|
|
|
return USDXformReader::get_local_usd_xform(time);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_)) {
|
|
|
|
|
if (skel_api.GetGeomBindTransformAttr().HasAuthoredValue()) {
|
|
|
|
|
pxr::GfMatrix4d bind_xf;
|
|
|
|
|
if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) {
|
|
|
|
|
/* The USD bind transform is a matrix of doubles,
|
|
|
|
|
* but we cast it to GfMatrix4f because Blender expects
|
|
|
|
|
* a matrix of floats. Also, we assume the transform
|
|
|
|
|
* is constant over time. */
|
|
|
|
|
return XformResult(pxr::GfMatrix4f(bind_xf), true);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
WM_reportf(RPT_WARNING,
|
|
|
|
|
"%s: Couldn't compute geom bind transform for %s",
|
|
|
|
|
__func__,
|
|
|
|
|
prim_.GetPath().GetAsString().c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return USDXformReader::get_local_usd_xform(time);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-03 11:55:53 +02:00
|
|
|
} // namespace blender::io::usd
|