USD Export: New Curves/Hair Support #105375

Merged
Michael Kowalski merged 19 commits from SonnyCampbell_Unity/blender:unity/T102376-USD-Curves-Export into main 2023-05-16 20:04:26 +02:00
2 changed files with 78 additions and 34 deletions
Showing only changes of commit a7a1a3d3ca - Show all commits

View File

@ -29,13 +29,9 @@
namespace blender::io::usd {
USDCurvesWriter::USDCurvesWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
{
}
USDCurvesWriter::USDCurvesWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
USDCurvesWriter::~USDCurvesWriter()
{
}
USDCurvesWriter::~USDCurvesWriter() {}
pxr::UsdGeomCurves USDCurvesWriter::DefineUsdGeomBasisCurves(pxr::VtValue curve_basis,
const bool is_cyclic,
@ -68,12 +64,13 @@ pxr::UsdGeomCurves USDCurvesWriter::DefineUsdGeomBasisCurves(pxr::VtValue curve_
static void populate_curve_widths(const bke::CurvesGeometry &geometry, pxr::VtArray<float> &widths)
{
const bke::AttributeAccessor curve_attributes = geometry.attributes();
const VArray<float> radii = curve_attributes.lookup<float>("radius", ATTR_DOMAIN_POINT);
const bke::AttributeReader<float> radii = curve_attributes.lookup<float>("radius",
ATTR_DOMAIN_POINT);
widths.resize(radii.size());
widths.resize(radii.varray.size());
for (const int i : radii.index_range()) {
widths[i] = radii[i] * 2.0f;
for (const int i : radii.varray.index_range()) {
widths[i] = radii.varray[i] * 2.0f;
}
}
@ -269,6 +266,8 @@ static void populate_curve_props_for_nurbs(const bke::CurvesGeometry &geometry,
pxr::TfToken &interpolation,
const bool is_cyclic)
{
/* Order and range, when representing a batched NurbsCurve should be authored one value per
* curve.*/
const int num_curves = geometry.curve_num;
orders.resize(num_curves);
@ -297,20 +296,23 @@ static void populate_curve_props_for_nurbs(const bke::CurvesGeometry &geometry,
Array<float> temp_knots(knots_num);
bke::curves::nurbs::calculate_knots(tot_points, mode, order, is_cyclic, temp_knots);
SonnyCampbell_Unity marked this conversation as resolved Outdated

Maybe I'm missing something, but doesn't this replace the knots completely with every new curve?

Maybe I'm missing something, but doesn't this replace the knots completely with every new curve?

Yeah you are right, thanks for catching this! I need to rethink what I was trying to do here.

Yeah you are right, thanks for catching this! I need to rethink what I was trying to do here.

I have resolved this issue with the knots now. According to the USD spec knots should be the concatentation of all batched curves so I've updated the logic there and added a test with a curve containing two NURBS curves.

https://openusd.org/dev/api/class_usd_geom_nurbs_curves.html#details

I have resolved this issue with the knots now. According to the USD spec `knots should be the concatentation of all batched curves` so I've updated the logic there and added a test with a curve containing two NURBS curves. https://openusd.org/dev/api/class_usd_geom_nurbs_curves.html#details
knots.resize(knots_num);
/* Knots should be the concatentation of all batched curves.
* https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
for (int i_knot = 0; i_knot < knots_num; i_knot++) {
knots[i_knot] = double(temp_knots[i_knot]);
knots.push_back(double(temp_knots[i_knot]));
}
/* Set end knots according to the USD spec for periodic curves
/* For USD it is required to set specific end knots for periodic/non-periodic curves
* https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
int zeroth_knot_index = knots.size() - knots_num;
if (is_cyclic) {
knots[0] = knots[1] - (knots[knots.size() - 2] - knots[knots.size() - 3]);
knots[knots.size() - 1] = knots[knots.size() - 2] + (knots[2] - knots[1]);
knots[zeroth_knot_index] = knots[zeroth_knot_index + 1] -
(knots[knots.size() - 2] - knots[knots.size() - 3]);
knots[knots.size() - 1] = knots[knots.size() - 2] +
(knots[zeroth_knot_index + 2] - knots[zeroth_knot_index + 1]);
}
else {
/* Set end knots according to the USD spec for non-periodic curves */
knots[0] = knots[1];
knots[zeroth_knot_index] = knots[zeroth_knot_index + 1];
knots[knots.size() - 1] = knots[knots.size() - 2];
}
}

View File

@ -55,8 +55,11 @@ static void check_bezier_curve(const pxr::UsdPrim bezier_prim,
static void check_nurbs_curve(const pxr::UsdPrim nurbs_prim,
SonnyCampbell_Unity marked this conversation as resolved Outdated

The above three functions (check_catmullRom_curve, check_bezier_curve, check_nurbs_curve) need to be marked as static to match their definition below, otherwise this code does not compile with clang on macOS.

The above three functions (`check_catmullRom_curve`, `check_bezier_curve`, `check_nurbs_curve`) need to be marked as static to match their definition below, otherwise this code does not compile with clang on macOS.
const int vertex_count,
const int knots_count,
const int order,
const bool is_periodic);
const int order);
static void check_nurbs_circle(const pxr::UsdPrim nurbs_prim,
const int vertex_count,
const int knots_count,
const int order);
class UsdCurvesTest : public BlendfileLoadingBaseTest {
protected:
@ -135,14 +138,14 @@ TEST_F(UsdCurvesTest, usd_export_curves)
std::string prim_name = pxr::TfMakeValidIdentifier("NurbsCurve");
pxr::UsdPrim test_prim = stage->GetPrimAtPath(pxr::SdfPath("/NurbsCurve/" + prim_name));
EXPECT_TRUE(test_prim.IsValid());
check_nurbs_curve(test_prim, 6, 10, 4, false);
check_nurbs_curve(test_prim, 6, 20, 4);
}
{
std::string prim_name = pxr::TfMakeValidIdentifier("NurbsCircle");
pxr::UsdPrim test_prim = stage->GetPrimAtPath(pxr::SdfPath("/NurbsCircle/" + prim_name));
EXPECT_TRUE(test_prim.IsValid());
check_nurbs_curve(test_prim, 8, 13, 3, true);
check_nurbs_circle(test_prim, 8, 13, 3);
}
{
@ -263,8 +266,7 @@ static void check_bezier_curve(const pxr::UsdPrim bezier_prim,
static void check_nurbs_curve(const pxr::UsdPrim nurbs_prim,
const int vertex_count,
const int knots_count,
const int order,
const bool is_periodic)
const int order)
{
auto curve = pxr::UsdGeomNurbsCurves(nurbs_prim);
@ -272,22 +274,19 @@ static void check_nurbs_curve(const pxr::UsdPrim nurbs_prim,
pxr::VtArray<int> orders;
order_attr.Get(&orders);
EXPECT_EQ(orders.size(), 1) << "Prim should only contain orders for a single curve";
EXPECT_EQ(orders[0], order) << "Curve should have order " << order;
EXPECT_EQ(orders.size(), 2) << "Prim should contain orders for two curves";
EXPECT_EQ(orders[0], order) << "Curves should have order " << order;
EXPECT_EQ(orders[1], order) << "Curves should have order " << order;
pxr::UsdAttribute knots_attr = curve.GetKnotsAttr();
pxr::VtArray<double> knots;
knots_attr.Get(&knots);
EXPECT_EQ(knots.size(), knots_count) << "Curve should have " << knots_count << " knots.";
if (is_periodic) {
EXPECT_EQ(knots[0], knots[1] - (knots[knots.size() - 2] - knots[knots.size() - 3]))
<< "NURBS curve should satisfy this knots rule for a periodic curve";
EXPECT_EQ(knots[knots.size() - 1], knots[knots.size() - 2] + (knots[2] - knots[1]))
<< "NURBS curve should satisfy this knots rule for a periodic curve";
}
else {
EXPECT_EQ(knots[0], knots[1])
for (int i = 0; i < 2; i++) {
int zeroth_knot_index = i * (knots_count / 2);
EXPECT_EQ(knots[zeroth_knot_index], knots[zeroth_knot_index + 1])
<< "NURBS curve should satisfy this knots rule for a nonperiodic curve";
EXPECT_EQ(knots[knots.size() - 1], knots[knots.size() - 2])
<< "NURBS curve should satisfy this knots rule for a nonperiodic curve";
@ -301,7 +300,50 @@ static void check_nurbs_curve(const pxr::UsdPrim nurbs_prim,
pxr::VtArray<int> vert_counts;
vert_count_attr.Get(&vert_counts);
EXPECT_EQ(vert_counts.size(), 1) << "Prim should only contains verts for a single curve";
EXPECT_EQ(vert_counts.size(), 2) << "Prim should contain verts for two curves";
EXPECT_EQ(vert_counts[0], vertex_count) << "Curve should have " << vertex_count << " verts.";
EXPECT_EQ(vert_counts[1], vertex_count) << "Curve should have " << vertex_count << " verts.";
}
/**
* Test that the provided prim is a valid NURBS curve. We also check it matches the expected
* wrap type, and has the expected number of vertices. For NURBS, we also validate that the knots
* layout matches the expected layout for periodic/non-periodic curves according to the USD spec.
*/
static void check_nurbs_circle(const pxr::UsdPrim nurbs_prim,
const int vertex_count,
const int knots_count,
const int order)
{
auto curve = pxr::UsdGeomNurbsCurves(nurbs_prim);
pxr::UsdAttribute order_attr = curve.GetOrderAttr();
pxr::VtArray<int> orders;
order_attr.Get(&orders);
EXPECT_EQ(orders.size(), 1) << "Prim should contain orders for one curves";
EXPECT_EQ(orders[0], order) << "Curve should have order " << order;
pxr::UsdAttribute knots_attr = curve.GetKnotsAttr();
pxr::VtArray<double> knots;
knots_attr.Get(&knots);
EXPECT_EQ(knots.size(), knots_count) << "Curve should have " << knots_count << " knots.";
EXPECT_EQ(knots[0], knots[1] - (knots[knots.size() - 2] - knots[knots.size() - 3]))
<< "NURBS curve should satisfy this knots rule for a periodic curve";
EXPECT_EQ(knots[knots.size() - 1], knots[knots.size() - 2] + (knots[2] - knots[1]))
<< "NURBS curve should satisfy this knots rule for a periodic curve";
auto widths_interp_token = curve.GetWidthsInterpolation();
EXPECT_EQ(widths_interp_token, pxr::UsdGeomTokens->vertex)
<< "Widths interpolation token should be vertex for NURBS curve";
pxr::UsdAttribute vert_count_attr = curve.GetCurveVertexCountsAttr();
pxr::VtArray<int> vert_counts;
vert_count_attr.Get(&vert_counts);
EXPECT_EQ(vert_counts.size(), 1) << "Prim should contain verts for one curve";
EXPECT_EQ(vert_counts[0], vertex_count) << "Curve should have " << vertex_count << " verts.";
}