Refactor Xform.

This commit is contained in:
Syoyo Fujita
2022-10-01 19:14:34 +09:00
parent ea0b8db3d1
commit d0c9bc99cd
16 changed files with 716 additions and 1071 deletions

View File

@@ -961,15 +961,14 @@ std::string print_xformOps(const std::vector<XformOp>& xformOps, const uint32_t
ss << pprint::Indent(indent);
// TODO: Check if `type_name` is set correctly.
ss << xformOp.type_name << " " ;
ss << xformOp.get_value_type_name() << " " ;
ss << to_string(xformOp.op);
if (!xformOp.suffix.empty()) {
ss << ":" << xformOp.suffix;
}
if (xformOp.IsTimeSamples()) {
if (xformOp.is_timesamples()) {
ss << ".timeSamples";
}

View File

@@ -1607,12 +1607,6 @@ class Property {
bool _has_custom{false}; // Qualified with 'custom' keyword?
};
// Orient: axis/angle expressed as a quaternion.
// NOTE: no `quath`, `matrix4f`
// using XformOpValueType =
// tinyusdz::variant<float, value::float3, value::quatf, double,
// value::double3, value::quatd, value::matrix4d>;
struct XformOp {
enum class OpType {
// matrix
@@ -1650,10 +1644,24 @@ struct XformOp {
// ":blender:pivot" for "xformOp:translate:blender:pivot". Suffix
// will be empty for "xformOp:translate"
// XformOpValueType value_type;
std::string type_name;
//std::string type_name;
value::TimeSamples var;
std::string get_value_type_name() const {
if (var.values.size() > 0) {
return var.values[0].type_name();
}
return "[InternalError] XformOp value type name";
}
uint32_t get_value_type_id() const {
if (var.values.size() > 0) {
return var.values[0].type_id();
}
return uint32_t(value::TypeId::TYPE_ID_INVALID);
}
// TODO: Check if T is valid type.
template <class T>
void set_scalar(const T &v) {
@@ -1661,32 +1669,39 @@ struct XformOp {
var.values.clear();
var.values.push_back(v);
type_name = value::TypeTrait<T>::type_name();
//type_name = value::TypeTrait<T>::type_name();
}
void set_timesamples(const value::TimeSamples &v) {
var = v;
if (var.values.size()) {
type_name = var.values[0].type_name();
}
//if (var.values.size()) {
// type_name = var.values[0].type_name();
//}
}
void set_timesamples(value::TimeSamples &&v) {
var = std::move(v);
if (var.values.size()) {
type_name = var.values[0].type_name();
}
//if (var.values.size()) {
// type_name = var.values[0].type_name();
//}
}
bool IsTimeSamples() const {
bool is_timesamples() const {
return (var.times.size() > 0) && (var.times.size() == var.values.size());
}
nonstd::optional<value::TimeSamples> get_timesamples() const {
if (is_timesamples()) {
return var;
}
return nonstd::nullopt;
}
// Type-safe way to get concrete value.
template <class T>
nonstd::optional<T> get_scalar_value() const {
if (IsTimeSamples()) {
if (is_timesamples()) {
return nonstd::nullopt;
}
@@ -1962,68 +1977,6 @@ struct Scope {
std::map<std::string, Property> props;
};
//
// For usdGeom, usdLux
// TODO: Move to `xform.hh`?
//
struct Xformable {
///
/// Evaluate XformOps and output evaluated(concatenated) matrix to `out_matrix`
/// `resetXformStack` become true when xformOps[0] is !resetXformStack!
/// Return error message when failed.
///
bool EvaluateXformOps(value::matrix4d *out_matrix, bool *resetXformStack, std::string *err) const;
///
/// Global = Parent x Local
///
nonstd::expected<value::matrix4d, std::string> GetGlobalMatrix(
const value::matrix4d &parentMatrix) const {
bool resetXformStack{false};
auto m = GetLocalMatrix(&resetXformStack);
if (m) {
if (resetXformStack) {
// Ignore parent's transform
// FIXME: Validate this is the correct way of handling !resetXformStack! op.
return m.value();
} else {
value::matrix4d cm =
Mult<value::matrix4d, double, 4>(parentMatrix, m.value());
return cm;
}
} else {
return nonstd::make_unexpected(m.error());
}
}
///
/// Evaluate xformOps and get local matrix.
///
nonstd::expected<value::matrix4d, std::string> GetLocalMatrix(bool *resetTransformStack = nullptr) const {
if (_dirty) {
value::matrix4d m;
std::string err;
if (EvaluateXformOps(&m, resetTransformStack, &err)) {
_matrix = m;
_dirty = false;
} else {
return nonstd::make_unexpected(err);
}
}
return _matrix;
}
void SetDirty(bool onoff) { _dirty = onoff; }
std::vector<XformOp> xformOps;
mutable bool _dirty{true};
mutable value::matrix4d _matrix; // Matrix of this Xform(local matrix)
};
nonstd::optional<Interpolation> InterpolationFromString(const std::string &v);
nonstd::optional<Orientation> OrientationFromString(const std::string &v);
nonstd::optional<Kind> KindFromString(const std::string &v);

View File

@@ -7,13 +7,13 @@
#include <sstream>
#include "common-macros.inc"
#include "pprinter.hh"
#include "prim-types.hh"
#include "tiny-format.hh"
#include "xform.hh"
//
#include "math-util.inc"
#include "common-macros.inc"
namespace tinyusdz {
@@ -234,533 +234,4 @@ nonstd::expected<bool, std::string> GeomMesh::ValidateGeomSubset() {
"TODO: Implent GeomMesh::ValidateGeomSubset\n");
}
namespace {
#if 0
value::matrix4d GetTransform(XformOp xform)
{
value::matrix4d m;
Identity(&m);
if (xform.op == XformOp::OpType::TRANSFORM) {
if (auto v = xform.value.get<value::matrix4d>()) {
m = v.value();
}
} else if (xform.op == XformOp::OpType::TRANSLATE) {
if (auto sf = xform.value.get<value::float3>()) {
m.m[3][0] = double(sf.value()[0]);
m.m[3][1] = double(sf.value()[1]);
m.m[3][2] = double(sf.value()[2]);
} else if (auto sd = xform.value.get<value::double3>()) {
m.m[3][0] = sd.value()[0];
m.m[3][1] = sd.value()[1];
m.m[3][2] = sd.value()[2];
}
} else if (xform.op == XformOp::OpType::SCALE) {
if (auto sf = xform.value.get<value::float3>()) {
m.m[0][0] = double(sf.value()[0]);
m.m[1][1] = double(sf.value()[1]);
m.m[2][2] = double(sf.value()[2]);
} else if (auto sd = xform.value.get<value::double3>()) {
m.m[0][0] = sd.value()[0];
m.m[1][1] = sd.value()[1];
m.m[2][2] = sd.value()[2];
}
} else {
DCOUT("TODO: xform.op = " << XformOp::GetOpTypeName(xform.op));
}
return m;
}
#endif
class XformEvaluator {
public:
XformEvaluator() { Identity(&m); }
XformEvaluator &RotateX(const double angle) { // in degrees
double rad = math::radian(angle);
value::matrix4d rm;
rm.m[1][1] = std::cos(rad);
rm.m[1][2] = std::sin(rad);
rm.m[2][1] = -std::sin(rad);
rm.m[2][2] = std::cos(rad);
m = Mult<value::matrix4d, double, 4>(m, rm);
return (*this);
}
XformEvaluator &RotateY(const double angle) { // in degrees
double rad = math::radian(angle);
value::matrix4d rm;
rm.m[0][0] = std::cos(rad);
rm.m[0][2] = -std::sin(rad);
rm.m[2][0] = std::sin(rad);
rm.m[2][2] = std::cos(rad);
m = Mult<value::matrix4d, double, 4>(m, rm);
return (*this);
}
XformEvaluator &RotateZ(const double angle) { // in degrees
double rad = math::radian(angle);
value::matrix4d rm;
rm.m[0][0] = std::cos(rad);
rm.m[0][1] = std::sin(rad);
rm.m[1][0] = -std::sin(rad);
rm.m[1][1] = std::cos(rad);
m = Mult<value::matrix4d, double, 4>(m, rm);
return (*this);
}
std::string error() const {
return err;
}
nonstd::expected<value::matrix4d, std::string> result() const {
if (err.empty()) {
return m;
}
return nonstd::make_unexpected(err);
}
std::string err;
value::matrix4d m;
};
} // namespace
bool Xformable::EvaluateXformOps(value::matrix4d *out_matrix,
bool *resetXformStack,
std::string *err) const {
value::matrix4d cm;
const auto RotateABC = [](const XformOp &x) -> nonstd::expected<value::matrix4d, std::string> {
value::double3 v;
if (auto h = x.get_scalar_value<value::half3>()) {
v[0] = double(half_to_float(h.value()[0]));
v[1] = double(half_to_float(h.value()[1]));
v[2] = double(half_to_float(h.value()[2]));
} else if (auto f = x.get_scalar_value<value::float3>()) {
v[0] = double(f.value()[0]);
v[1] = double(f.value()[1]);
v[2] = double(f.value()[2]);
} else if (auto d = x.get_scalar_value<value::double3>()) {
v = d.value();
} else {
if (x.suffix.empty()) {
return nonstd::make_unexpected(fmt::format("`{}` is not half3, float3 or double3 type.\n", to_string(x.op)));
} else {
return nonstd::make_unexpected(fmt::format("`{}:{}` is not half3, float3 or double3 type.\n", to_string(x.op), x.suffix));
}
}
// invert input, and compute concatenated matrix
// inv(ABC) = inv(A) x inv(B) x inv(C)
// as done in pxrUSD.
if (x.inverted) {
v[0] = -v[0];
v[1] = -v[1];
v[2] = -v[2];
}
double xAngle = v[0];
double yAngle = v[1];
double zAngle = v[2];
XformEvaluator eval;
if (x.inverted) {
if (x.op == XformOp::OpType::RotateXYZ) {
eval.RotateZ(zAngle);
eval.RotateY(yAngle);
eval.RotateX(xAngle);
} else if (x.op == XformOp::OpType::RotateXZY) {
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
} else if (x.op == XformOp::OpType::RotateYXZ) {
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateYZX) {
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateZYX) {
eval.RotateX(xAngle);
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
} else if (x.op == XformOp::OpType::RotateZXY) {
eval.RotateY(yAngle);
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
} else {
/// ???
return nonstd::make_unexpected("[InternalError] RotateABC");
}
} else {
if (x.op == XformOp::OpType::RotateXYZ) {
eval.RotateX(xAngle);
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
} else if (x.op == XformOp::OpType::RotateXZY) {
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateYXZ) {
eval.RotateY(yAngle);
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
} else if (x.op == XformOp::OpType::RotateYZX) {
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
} else if (x.op == XformOp::OpType::RotateZYX) {
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateZXY) {
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
eval.RotateY(yAngle);
} else {
/// ???
return nonstd::make_unexpected("[InternalError] RotateABC");
}
}
return eval.result();
};
// Concat matrices
for (size_t i = 0; i < xformOps.size(); i++) {
const auto x = xformOps[i];
value::matrix4d m;
Identity(&m);
(void)x;
if (x.IsTimeSamples()) {
if (err) {
(*err) += "TODO: xformOp property with timeSamples.\n";
}
return false;
}
switch (x.op) {
case XformOp::OpType::ResetXformStack: {
if (i != 0) {
if (err) {
(*err) +=
"!resetXformStack! should only appear at the first element of "
"xformOps\n";
}
return false;
}
// Notify resetting previous(parent node's) matrices
if (resetXformStack) {
(*resetXformStack) = true;
}
break;
}
case XformOp::OpType::Transform: {
if (auto sxf = x.get_scalar_value<value::matrix4f>()) {
value::matrix4f mf = sxf.value();
for (size_t j = 0; j < 4; j++) {
for (size_t k = 0; k < 4; k++) {
m.m[j][k] = double(mf.m[j][k]);
}
}
} else if (auto sxd = x.get_scalar_value<value::matrix4d>()) {
m = sxd.value();
} else {
if (err) {
(*err) += "`xformOp:transform` is not matrix4f or matrix4d type.\n";
}
return false;
}
if (x.inverted) {
// Singular check.
// pxrUSD uses 1e-9
double det = determinant(m);
if (std::fabs(det) < 1e-9) {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:transform` is singular matrix and cannot be inverted.\n";
} else {
(*err) += fmt::format("`xformOp:transform:{}` is singular matrix and cannot be inverted.\n", x.suffix);
}
}
return false;
}
m = invert(m);
}
break;
}
case XformOp::OpType::Scale: {
double sx, sy, sz;
if (auto sxh = x.get_scalar_value<value::half3>()) {
sx = double(half_to_float(sxh.value()[0]));
sy = double(half_to_float(sxh.value()[1]));
sz = double(half_to_float(sxh.value()[2]));
} else if (auto sxf = x.get_scalar_value<value::float3>()) {
sx = double(sxf.value()[0]);
sy = double(sxf.value()[1]);
sz = double(sxf.value()[2]);
} else if (auto sxd = x.get_scalar_value<value::double3>()) {
sx = sxd.value()[0];
sy = sxd.value()[1];
sz = sxd.value()[2];
} else {
if (err) {
(*err) += "`xformOp:scale` is not half3, float3 or double3 type.\n";
}
return false;
}
if (x.inverted) {
// FIXME: Safe division
sx = 1.0 / sx;
sy = 1.0 / sy;
sz = 1.0 / sz;
}
m.m[0][0] = sx;
m.m[1][1] = sy;
m.m[2][2] = sz;
break;
}
case XformOp::OpType::Translate: {
double tx, ty, tz;
if (auto txh = x.get_scalar_value<value::half3>()) {
tx = double(half_to_float(txh.value()[0]));
ty = double(half_to_float(txh.value()[1]));
tz = double(half_to_float(txh.value()[2]));
} else if (auto txf = x.get_scalar_value<value::float3>()) {
tx = double(txf.value()[0]);
ty = double(txf.value()[1]);
tz = double(txf.value()[2]);
} else if (auto txd = x.get_scalar_value<value::double3>()) {
tx = txd.value()[0];
ty = txd.value()[1];
tz = txd.value()[2];
} else {
if (err) {
(*err) += "`xformOp:translate` is not half3, float3 or double3 type.\n";
}
return false;
}
if (x.inverted) {
tx = -tx;
ty = -ty;
tz = -tz;
}
m.m[3][0] = tx;
m.m[3][1] = ty;
m.m[3][2] = tz;
break;
}
// FIXME: Validate ROTATE_X, _Y, _Z implementation
case XformOp::OpType::RotateX: {
double angle; // in degrees
if (auto h = x.get_scalar_value<value::half>()) {
angle = double(half_to_float(h.value()));
} else if (auto f = x.get_scalar_value<float>()) {
angle = double(f.value());
} else if (auto d = x.get_scalar_value<double>()) {
angle = d.value();
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:rotateX` is not half, float or double type.\n";
} else {
(*err) += fmt::format("`xformOp:rotateX:{}` is not half, float or double type.\n", x.suffix);
}
}
return false;
}
XformEvaluator xe;
xe.RotateX(angle);
auto ret = xe.result();
if (ret) {
m = ret.value();
} else {
if (err) {
(*err) += ret.error();
}
return false;
}
break;
}
case XformOp::OpType::RotateY: {
double angle; // in degrees
if (auto h = x.get_scalar_value<value::half>()) {
angle = double(half_to_float(h.value()));
} else if (auto f = x.get_scalar_value<float>()) {
angle = double(f.value());
} else if (auto d = x.get_scalar_value<double>()) {
angle = d.value();
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:rotateY` is not half, float or double type.\n";
} else {
(*err) += fmt::format("`xformOp:rotateY:{}` is not half, float or double type.\n", x.suffix);
}
}
return false;
}
XformEvaluator xe;
xe.RotateY(angle);
auto ret = xe.result();
if (ret) {
m = ret.value();
} else {
if (err) {
(*err) += ret.error();
}
return false;
}
break;
}
case XformOp::OpType::RotateZ: {
double angle; // in degrees
if (auto h = x.get_scalar_value<value::half>()) {
angle = double(half_to_float(h.value()));
} else if (auto f = x.get_scalar_value<float>()) {
angle = double(f.value());
} else if (auto d = x.get_scalar_value<double>()) {
angle = d.value();
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:rotateZ` is not half, float or double type.\n";
} else {
(*err) += fmt::format("`xformOp:rotateZ:{}` is not half, float or double type.\n", x.suffix);
}
}
return false;
}
XformEvaluator xe;
xe.RotateZ(angle);
auto ret = xe.result();
if (ret) {
m = ret.value();
} else {
if (err) {
(*err) += ret.error();
}
return false;
}
break;
}
case XformOp::OpType::Orient: {
// value::quat stores elements in (x, y, z, w)
// linalg::quat also stores elements in (x, y, z, w)
value::matrix3d rm;
if (auto h = x.get_scalar_value<value::quath>()) {
rm = to_matrix3x3(h.value());
} else if (auto f = x.get_scalar_value<value::quatf>()) {
rm = to_matrix3x3(f.value());
} else if (auto d = x.get_scalar_value<value::quatd>()) {
rm = to_matrix3x3(d.value());
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:orient` is not quath, quatf or quatd type.\n";
} else {
(*err) += fmt::format("`xformOp:orient:{}` is not quath, quatf or quatd type.\n", x.suffix);
}
}
return false;
}
// FIXME: invert before getting matrix.
if (x.inverted) {
value::matrix3d inv_rm;
if (invert3x3(rm, inv_rm)) {
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:orient` is singular and cannot be inverted.\n";
} else {
(*err) += fmt::format("`xformOp:orient:{}` is singular and cannot be inverted.\n", x.suffix);
}
}
}
rm = inv_rm;
}
m = to_matrix(rm, {0.0, 0.0, 0.0});
break;
}
case XformOp::OpType::RotateXYZ:
case XformOp::OpType::RotateXZY:
case XformOp::OpType::RotateYXZ:
case XformOp::OpType::RotateYZX:
case XformOp::OpType::RotateZXY:
case XformOp::OpType::RotateZYX: {
auto ret = RotateABC(x);
if (!ret) {
(*err) += ret.error();
return false;
}
m = ret.value();
}
}
cm = Mult<value::matrix4d, double, 4>(cm, m);
}
(*out_matrix) = cm;
return true;
}
} // namespace tinyusdz

View File

@@ -6,6 +6,7 @@
#include "prim-types.hh"
#include "value-types.hh"
#include "xform.hh"
namespace tinyusdz {

View File

@@ -5,6 +5,7 @@
#pragma once
#include "prim-types.hh"
#include "xform.hh"
namespace tinyusdz {
@@ -41,7 +42,7 @@ struct LuxSphereLight : public Xformable {
// rel light:filters
TypedAttributeWithFallback<Animatable<float>> radius{0.5f}; // inputs:radius
TypedAttribute<Animatable<Extent>> extent; // float3[]
TypedAttribute<Animatable<Extent>> extent; // float3[]
//
// Properties
@@ -76,7 +77,7 @@ struct LuxCylinderLight : public Xformable {
TypedAttributeWithFallback<Animatable<float>> length{1.0f}; // inputs:length size in Y axis
TypedAttributeWithFallback<Animatable<float>> radius{0.5f}; // inputs:radius size in X axis
TypedAttribute<Animatable<Extent>> extent; // float3[]
TypedAttribute<Animatable<Extent>> extent; // float3[]
// asset inputs:texture:file
@@ -115,7 +116,7 @@ struct LuxRectLight : public Xformable {
TypedAttribute<Animatable<value::AssetPath>> file; // asset inputs:texture:file
TypedAttributeWithFallback<Animatable<float>> height{1.0f}; // inputs:height size in Y axis
TypedAttributeWithFallback<Animatable<float>> width{1.0f}; // inputs:width size in X axis
TypedAttribute<Animatable<Extent>> extent; // float3[]
TypedAttribute<Animatable<Extent>> extent; // float3[]
// asset inputs:texture:file
@@ -151,7 +152,7 @@ struct LuxDiskLight : public Xformable {
// rel light:filters
TypedAttributeWithFallback<Animatable<float>> radius{0.5f}; // inputs:radius
TypedAttribute<Animatable<Extent>> extent; // float3[]
TypedAttribute<Animatable<Extent>> extent; // float3[]
// asset inputs:texture:file
@@ -187,7 +188,7 @@ struct LuxDistantLight : public Xformable {
// rel light:filters
TypedAttributeWithFallback<Animatable<float>> angle{0.53f}; // inputs:angle in degrees
TypedAttribute<Animatable<Extent>> extent; // float3[]
TypedAttribute<Animatable<Extent>> extent; // float3[]
// asset inputs:texture:file
@@ -234,7 +235,7 @@ struct LuxDomeLight : public Xformable {
TypedAttributeWithFallback<Animatable<float>> guideRadius{1.0e5f};
// asset inputs:texture:file
TextureFormat textureFormat{TextureFormat::Automatic}; // token inputs:texture:format
TypedAttribute<Animatable<Extent>> extent; // float3[]
TypedAttribute<Animatable<Extent>> extent; // float3[]
// rel portals
// rel proxyPrim
@@ -258,7 +259,7 @@ struct LuxGeometryLight : public Xformable {
// TODO
struct LuxPortalLight : public Xformable {
Specifier spec;
TypedAttribute<Animatable<Extent>> extent; // float3[]
TypedAttribute<Animatable<Extent>> extent; // float3[]
};
// TODO

View File

@@ -104,11 +104,19 @@ namespace usdc {
constexpr auto kTag = "[USDC]";
struct PrimNode {
value::Value prim;
value::Value prim; // Usually stores reconstructed(composed) concrete Prim(e.g. Xform)
int64_t parent{-1}; // -1 = root node
std::vector<size_t> children; // index to USDCReader::Impl::._prim_nodes[]
//
//
//
std::vector<value::token> primChildren;
//
// For Variants
///
// SpecType::VariantSet
std::vector<value::token> variantChildren;
@@ -2107,449 +2115,6 @@ bool USDCReader::Impl::ReconstructPrimRecursively(
return true;
}
#if 0
bool USDCReader::Impl::ReconstructPrimTree(
const PathIndexToSpecIndexMap &psmap, Stage *stage) {
// TODO: Use stack-free implementation?
// (parent, currentPrimIndices)
std::stack<std::pair<int32_t, std::vector<uint32_t>>> primIndicesStack;
{ // root
std::vector<uint32_t> rootPrims;
rootPrims.push_back(0);
primIndicesStack.push({-1, rootPrims});
}
while (primIndicesStack.size()) {
uint32_t level = primIndicesStack.size();
if (level > _config.kMaxPrimNestLevel) {
PUSH_ERROR_AND_RETURN_TAG(kTag, "Prim hierarchy is too deep.");
}
auto stackItem = primIndicesStack.pop();
int32_t parentPrimIndex = std::get<0>(stackItem);
const std::vector<uint32_t> currentPrimIndices = std::get<1>(stackItem);
for (const uint32_t currentPrimIndex : currentPrimIndices) {
DCOUT("ReconstructPrimRecursively: parent = "
<< std::to_string(currentPrimIndex) << ", level = " << std::to_string(level));
if ((currentPrimIndex < 0) || (currentPrimIndex >= int(_nodes.size()))) {
PUSH_ERROR("Invalid currentPrimIndex node id: " + std::to_string(currentPrimIndex) +
". Must be in range [0, " + std::to_string(_nodes.size()) + ")");
return false;
}
const crate::CrateReader::Node &node = _nodes[size_t(currentPrimIndex)];
#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT
std::cout << pprint::Indent(uint32_t(level)) << "lv[" << level
<< "] node_index[" << currentPrimIndex << "] " << node.GetLocalPath()
<< " ==\n";
std::cout << pprint::Indent(uint32_t(level)) << " childs = [";
for (size_t i = 0; i < node.GetChildren().size(); i++) {
std::cout << node.GetChildren()[i];
if (i != (node.GetChildren().size() - 1)) {
std::cout << ", ";
}
}
std::cout << "]\n";
#endif
if (!psmap.count(uint32_t(currentPrimIndex))) {
// No specifier assigned to this node.
DCOUT("No specifier assigned to this node: " << currentPrimIndex);
return true; // would be OK.
}
uint32_t spec_index = psmap.at(uint32_t(currentPrimIndex));
if (spec_index >= _specs.size()) {
PUSH_ERROR("Invalid specifier id: " + std::to_string(spec_index) +
". Must be in range [0, " + std::to_string(_specs.size()) + ")");
return false;
}
const crate::Spec &spec = _specs[spec_index];
DCOUT(pprint::Indent(uint32_t(level))
<< " specTy = " << to_string(spec.spec_type));
DCOUT(pprint::Indent(uint32_t(level))
<< " fieldSetIndex = " << spec.fieldset_index.value);
if ((spec.spec_type == SpecType::Attribute) ||
(spec.spec_type == SpecType::Relationship)) {
if (_prim_table.count(parent)) {
// This node is a Properties node. These are processed in
// ReconstructPrim(), so nothing to do here.
return true;
}
}
if (!_live_fieldsets.count(spec.fieldset_index)) {
PUSH_ERROR("FieldSet id: " + std::to_string(spec.fieldset_index.value) +
" must exist in live fieldsets.");
return false;
}
const crate::FieldValuePairVector &fvs =
_live_fieldsets.at(spec.fieldset_index);
if (fvs.size() > _config.kMaxFieldValuePairs) {
PUSH_ERROR_AND_RETURN_TAG(kTag, "Too much FieldValue pairs.");
}
// DBG
for (auto &fv : fvs) {
DCOUT("parent[" << currentPrimIndex << "] level [" << level << "] fv name "
<< fv.first << "(type = " << fv.second.type_name() << ")");
}
nonstd::optional<Prim> prim;
std::vector<value::token> primChildren;
Path elemPath;
// StageMeta = root only attributes.
// TODO: Unify reconstrction code with USDAReder?
if (currentPrimIndex == 0) {
if (const auto &pv = GetElemPath(crate::Index(uint32_t(currentPrimIndex)))) {
DCOUT("Root element path: " << pv.value().full_path_name());
} else {
PUSH_ERROR_AND_RETURN("(Internal error). Root Element Path not found.");
}
// Root layer(Stage) is PseudoRoot.
if (spec.spec_type != SpecType::PseudoRoot) {
PUSH_ERROR_AND_RETURN(
"SpecTypePseudoRoot expected for root layer(Stage) element.");
}
if (!ReconstrcutStageMeta(fvs, &stage->GetMetas(), &primChildren)) {
PUSH_ERROR_AND_RETURN("Failed to reconstruct StageMeta.");
}
_prim_table.insert(currentPrimIndex);
} else {
nonstd::optional<std::string> typeName;
nonstd::optional<Specifier> specifier;
std::vector<value::token> properties;
nonstd::optional<bool> active;
nonstd::optional<bool> hidden;
nonstd::optional<APISchemas> apiSchemas;
nonstd::optional<Kind> kind;
nonstd::optional<CustomDataType> assetInfo;
nonstd::optional<StringData> doc;
nonstd::optional<StringData> comment;
///
///
/// Prim(Model) fieldSet example.
///
///
/// specTy = SpecTypePrim
///
/// - specifier(specifier) : e.g. `def`, `over`, ...
/// - kind(token) : kind metadataum
/// - optional: typeName(token) : type name of Prim(e.g. `Xform`). No
/// typeName = `def "mynode"`
/// - properties(token[]) : List of name of Prim properties(attributes)
/// - optional: primChildren(token[]): List of child prims.
///
///
/// Attrib fieldSet example
///
/// specTyppe = SpecTypeAttribute
///
/// - typeName(token) : type name of Attribute(e.g. `float`)
/// - custom(bool) : `custom` qualifier
/// - variability(variability) : Variability(meta?)
/// <value>
/// - default : Default(fallback) value.
/// - timeSample(TimeSamples) : `.timeSamples` data.
/// - connectionPaths(type = ListOpPath) : `.connect`
/// - (Empty) : Define only(Neiher connection nor value assigned. e.g.
/// "float outputs:rgb")
DCOUT("---");
// Fields for Prim and Prim metas.
for (const auto &fv : fvs) {
if (fv.first == "typeName") {
if (auto pv = fv.second.get_value<value::token>()) {
typeName = pv.value().str();
DCOUT("typeName = " << typeName.value());
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`typeName` must be type `token`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "specifier") {
if (auto pv = fv.second.get_value<Specifier>()) {
specifier = pv.value();
DCOUT("specifier = " << to_string(specifier.value()));
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`specifier` must be type `Specifier`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "properties") {
if (auto pv = fv.second.get_value<std::vector<value::token>>()) {
properties = pv.value();
DCOUT("properties = " << properties);
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`properties` must be type `token[]`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "primChildren") {
if (auto pv = fv.second.get_value<std::vector<value::token>>()) {
// We can ignore primChildren for now
// PUSH_WARN("We can ignore `primChildren` for now");
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`primChildren` must be type `token[]`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "active") {
if (auto pv = fv.second.get_value<bool>()) {
active = pv.value();
DCOUT("active = " << to_string(active.value()));
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`active` must be type `bool, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "hidden") {
if (auto pv = fv.second.get_value<bool>()) {
hidden = pv.value();
DCOUT("hidden = " << to_string(hidden.value()));
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`hidden` must be type `bool`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "assetInfo") {
// CustomData(dict)
if (auto pv = fv.second.get_value<CustomDataType>()) {
assetInfo = pv.value();
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`assetInfo` must be type `dictionary`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "kind") {
if (auto pv = fv.second.get_value<value::token>()) {
if (auto kv = KindFromString(pv.value().str())) {
kind = kv.value();
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, fmt::format("Invalid token for `kind` Prim metadata: `{}`", pv.value().str()));
}
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`kind` must be type `token`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "apiSchemas") {
if (auto pv = fv.second.get_value<ListOp<value::token>>()) {
auto listop = pv.value();
auto ret = ToAPISchemas(listop);
if (!ret) {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "Failed to validate `apiSchemas`: " + ret.error());
} else {
apiSchemas = (*ret);
}
// DCOUT("apiSchemas = " << to_string(listop));
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`apiSchemas` must be type `ListOp[Token]`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "documentation") {
if (auto pv = fv.second.get_value<std::string>()) {
StringData s;
s.value = pv.value();
s.is_triple_quoted = hasNewline(s.value);
doc = s;
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`documentation` must be type `string`, but got type `"
<< fv.second.type_name() << "`");
}
} else if (fv.first == "comment") {
if (auto pv = fv.second.get_value<std::string>()) {
StringData s;
s.value = pv.value();
s.is_triple_quoted = hasNewline(s.value);
comment = s;
} else {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "`comment` must be type `string`, but got type `"
<< fv.second.type_name() << "`");
}
} else {
DCOUT("PrimProp TODO: " << fv.first);
PUSH_WARN("PrimProp TODO: " << fv.first);
}
}
DCOUT("===");
#define RECONSTRUCT_PRIM(__primty, __node_ty, __prim_name) \
if (__node_ty == value::TypeTrait<__primty>::type_name()) { \
__primty typed_prim; \
if (!ReconstructPrim(node, fvs, psmap, &typed_prim)) { \
PUSH_ERROR_AND_RETURN_TAG(kTag, \
"Failed to reconstruct Prim " << __node_ty); \
} \
/* TODO: Better Prim meta handling */ \
if (active) { \
typed_prim.meta.active = active.value(); \
} \
if (hidden) { \
typed_prim.meta.hidden = hidden.value(); \
} \
if (apiSchemas) { \
typed_prim.meta.apiSchemas = apiSchemas.value(); \
} \
if (kind) { \
typed_prim.meta.kind = kind.value(); \
} \
if (assetInfo) { \
typed_prim.meta.assetInfo = assetInfo.value(); \
} \
if (doc) { \
typed_prim.meta.doc = doc.value(); \
} \
if (comment) { \
typed_prim.meta.comment = comment.value(); \
} \
typed_prim.name = __prim_name; \
value::Value primdata = typed_prim; \
prim = Prim(primdata); \
} else
if (spec.spec_type == SpecType::Prim) {
// Prim
if (const auto &pv = GetElemPath(crate::Index(uint32_t(currentPrimIndex)))) {
elemPath = pv.value();
DCOUT(fmt::format("Element path: {}", elemPath.full_path_name()));
} else {
PUSH_ERROR_AND_RETURN_TAG(kTag,
"(Internal errror) Element path not found.");
}
// Sanity check
if (specifier) {
if (specifier.value() != Specifier::Def) {
PUSH_ERROR_AND_RETURN_TAG(
kTag, "Currently TinyUSDZ only supports `def` for `specifier`.");
}
} else {
PUSH_ERROR_AND_RETURN_TAG(kTag,
"`specifier` field is missing for FieldSets "
"with SpecType::Prim.");
}
if (!typeName) {
PUSH_WARN("Treat this node as Model(where `typeName` is missing.");
typeName = "Model";
}
if (typeName) {
std::string prim_name = elemPath.GetPrimPart();
RECONSTRUCT_PRIM(Xform, typeName.value(), prim_name)
RECONSTRUCT_PRIM(Model, typeName.value(), prim_name)
RECONSTRUCT_PRIM(Scope, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomMesh, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomPoints, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomCylinder, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomCube, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomCone, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomSphere, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomCapsule, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomBasisCurves, typeName.value(), prim_name)
RECONSTRUCT_PRIM(GeomCamera, typeName.value(), prim_name)
// RECONSTRUCT_PRIM(GeomSubset, typeName.value(), prim_name)
RECONSTRUCT_PRIM(LuxSphereLight, typeName.value(), prim_name)
RECONSTRUCT_PRIM(LuxDomeLight, typeName.value(), prim_name)
RECONSTRUCT_PRIM(LuxCylinderLight, typeName.value(), prim_name)
RECONSTRUCT_PRIM(LuxDiskLight, typeName.value(), prim_name)
RECONSTRUCT_PRIM(LuxDistantLight, typeName.value(), prim_name)
RECONSTRUCT_PRIM(SkelRoot, typeName.value(), prim_name)
RECONSTRUCT_PRIM(Skeleton, typeName.value(), prim_name)
RECONSTRUCT_PRIM(SkelAnimation, typeName.value(), prim_name)
RECONSTRUCT_PRIM(Shader, typeName.value(), prim_name)
RECONSTRUCT_PRIM(Material, typeName.value(), prim_name)
{
PUSH_WARN(
"TODO or we can ignore this typeName: " << typeName.value());
}
if (prim) {
// Prim name
prim.value().elementPath = elemPath;
}
}
DCOUT("add prim idx " << currentPrimIndex);
if (_prim_table.count(currentPrimIndex)) {
DCOUT("??? prim idx already set " << currentPrimIndex);
} else {
_prim_table.insert(currentPrimIndex);
}
} else {
PUSH_ERROR_AND_RETURN_TAG(kTag,
"TODO: specTy = " << to_string(spec.spec_type));
}
}
// null : parent node is Property or other Spec type.
// non-null : parent node is Prim
Prim *currPrimPtr = nullptr;
if (prim) {
currPrimPtr = &(prim.value());
}
{
DCOUT("node.Children.size = " << node.GetChildren().size());
for (size_t i = 0; i < node.GetChildren().size(); i++) {
DCOUT("Reconstuct Prim children: " << i << " / " << node.GetChildren().size());
if (!ReconstructPrimRecursively(currentPrimIndex, int(node.GetChildren()[i]),
currPrimPtr, level + 1, psmap, stage)) {
return false;
}
DCOUT("DONE Reconstuct Prim children: " << i << " / " << node.GetChildren().size());
}
}
if (parentPrimIndex == 0) { // root prim
if (prim) {
stage->GetRootPrims().emplace_back(std::move(prim.value()));
}
} else {
// Add to root prim.
if (prim && rootPrim) {
rootPrim->children.emplace_back(std::move(prim.value()));
}
}
}
}
return true;
}
#endif
bool USDCReader::Impl::ReconstructStage(Stage *stage) {
(void)stage;

View File

@@ -16,6 +16,8 @@
#include "prim-types.hh"
#include "math-util.inc"
#include "xform.hh"
#include "tiny-format.hh"
#include "pprinter.hh"
namespace tinyusdz {
@@ -227,4 +229,498 @@ bool invert3x3(const value::matrix3d &_m, value::matrix3d &inv_m) {
return true;
}
namespace {
///
/// Xform evaluation with method chain style.
///
class XformEvaluator {
public:
XformEvaluator() { Identity(&m); }
XformEvaluator &RotateX(const double angle) { // in degrees
double rad = math::radian(angle);
value::matrix4d rm;
rm.m[1][1] = std::cos(rad);
rm.m[1][2] = std::sin(rad);
rm.m[2][1] = -std::sin(rad);
rm.m[2][2] = std::cos(rad);
m = Mult<value::matrix4d, double, 4>(m, rm);
return (*this);
}
XformEvaluator &RotateY(const double angle) { // in degrees
double rad = math::radian(angle);
value::matrix4d rm;
rm.m[0][0] = std::cos(rad);
rm.m[0][2] = -std::sin(rad);
rm.m[2][0] = std::sin(rad);
rm.m[2][2] = std::cos(rad);
m = Mult<value::matrix4d, double, 4>(m, rm);
return (*this);
}
XformEvaluator &RotateZ(const double angle) { // in degrees
double rad = math::radian(angle);
value::matrix4d rm;
rm.m[0][0] = std::cos(rad);
rm.m[0][1] = std::sin(rad);
rm.m[1][0] = -std::sin(rad);
rm.m[1][1] = std::cos(rad);
m = Mult<value::matrix4d, double, 4>(m, rm);
return (*this);
}
std::string error() const {
return err;
}
nonstd::expected<value::matrix4d, std::string> result() const {
if (err.empty()) {
return m;
}
return nonstd::make_unexpected(err);
}
std::string err;
value::matrix4d m;
};
} // namespace local
bool Xformable::EvaluateXformOps(value::matrix4d *out_matrix,
bool *resetXformStack,
std::string *err) const {
value::matrix4d cm;
const auto RotateABC = [](const XformOp &x) -> nonstd::expected<value::matrix4d, std::string> {
value::double3 v;
if (auto h = x.get_scalar_value<value::half3>()) {
v[0] = double(half_to_float(h.value()[0]));
v[1] = double(half_to_float(h.value()[1]));
v[2] = double(half_to_float(h.value()[2]));
} else if (auto f = x.get_scalar_value<value::float3>()) {
v[0] = double(f.value()[0]);
v[1] = double(f.value()[1]);
v[2] = double(f.value()[2]);
} else if (auto d = x.get_scalar_value<value::double3>()) {
v = d.value();
} else {
if (x.suffix.empty()) {
return nonstd::make_unexpected(fmt::format("`{}` is not half3, float3 or double3 type.\n", to_string(x.op)));
} else {
return nonstd::make_unexpected(fmt::format("`{}:{}` is not half3, float3 or double3 type.\n", to_string(x.op), x.suffix));
}
}
// invert input, and compute concatenated matrix
// inv(ABC) = inv(A) x inv(B) x inv(C)
// as done in pxrUSD.
if (x.inverted) {
v[0] = -v[0];
v[1] = -v[1];
v[2] = -v[2];
}
double xAngle = v[0];
double yAngle = v[1];
double zAngle = v[2];
XformEvaluator eval;
if (x.inverted) {
if (x.op == XformOp::OpType::RotateXYZ) {
eval.RotateZ(zAngle);
eval.RotateY(yAngle);
eval.RotateX(xAngle);
} else if (x.op == XformOp::OpType::RotateXZY) {
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
} else if (x.op == XformOp::OpType::RotateYXZ) {
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateYZX) {
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateZYX) {
eval.RotateX(xAngle);
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
} else if (x.op == XformOp::OpType::RotateZXY) {
eval.RotateY(yAngle);
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
} else {
/// ???
return nonstd::make_unexpected("[InternalError] RotateABC");
}
} else {
if (x.op == XformOp::OpType::RotateXYZ) {
eval.RotateX(xAngle);
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
} else if (x.op == XformOp::OpType::RotateXZY) {
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateYXZ) {
eval.RotateY(yAngle);
eval.RotateX(xAngle);
eval.RotateZ(zAngle);
} else if (x.op == XformOp::OpType::RotateYZX) {
eval.RotateY(yAngle);
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
} else if (x.op == XformOp::OpType::RotateZYX) {
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
eval.RotateY(yAngle);
} else if (x.op == XformOp::OpType::RotateZXY) {
eval.RotateZ(zAngle);
eval.RotateX(xAngle);
eval.RotateY(yAngle);
} else {
/// ???
return nonstd::make_unexpected("[InternalError] RotateABC");
}
}
return eval.result();
};
// Concat matrices
for (size_t i = 0; i < xformOps.size(); i++) {
const auto x = xformOps[i];
value::matrix4d m;
Identity(&m);
(void)x;
if (x.is_timesamples()) {
if (err) {
(*err) += "TODO: xformOp property with timeSamples.\n";
}
return false;
}
switch (x.op) {
case XformOp::OpType::ResetXformStack: {
if (i != 0) {
if (err) {
(*err) +=
"!resetXformStack! should only appear at the first element of "
"xformOps\n";
}
return false;
}
// Notify resetting previous(parent node's) matrices
if (resetXformStack) {
(*resetXformStack) = true;
}
break;
}
case XformOp::OpType::Transform: {
if (auto sxf = x.get_scalar_value<value::matrix4f>()) {
value::matrix4f mf = sxf.value();
for (size_t j = 0; j < 4; j++) {
for (size_t k = 0; k < 4; k++) {
m.m[j][k] = double(mf.m[j][k]);
}
}
} else if (auto sxd = x.get_scalar_value<value::matrix4d>()) {
m = sxd.value();
} else {
if (err) {
(*err) += "`xformOp:transform` is not matrix4f or matrix4d type.\n";
}
return false;
}
if (x.inverted) {
// Singular check.
// pxrUSD uses 1e-9
double det = determinant(m);
if (std::fabs(det) < 1e-9) {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:transform` is singular matrix and cannot be inverted.\n";
} else {
(*err) += fmt::format("`xformOp:transform:{}` is singular matrix and cannot be inverted.\n", x.suffix);
}
}
return false;
}
m = invert(m);
}
break;
}
case XformOp::OpType::Scale: {
double sx, sy, sz;
if (auto sxh = x.get_scalar_value<value::half3>()) {
sx = double(half_to_float(sxh.value()[0]));
sy = double(half_to_float(sxh.value()[1]));
sz = double(half_to_float(sxh.value()[2]));
} else if (auto sxf = x.get_scalar_value<value::float3>()) {
sx = double(sxf.value()[0]);
sy = double(sxf.value()[1]);
sz = double(sxf.value()[2]);
} else if (auto sxd = x.get_scalar_value<value::double3>()) {
sx = sxd.value()[0];
sy = sxd.value()[1];
sz = sxd.value()[2];
} else {
if (err) {
(*err) += "`xformOp:scale` is not half3, float3 or double3 type.\n";
}
return false;
}
if (x.inverted) {
// FIXME: Safe division
sx = 1.0 / sx;
sy = 1.0 / sy;
sz = 1.0 / sz;
}
m.m[0][0] = sx;
m.m[1][1] = sy;
m.m[2][2] = sz;
break;
}
case XformOp::OpType::Translate: {
double tx, ty, tz;
if (auto txh = x.get_scalar_value<value::half3>()) {
tx = double(half_to_float(txh.value()[0]));
ty = double(half_to_float(txh.value()[1]));
tz = double(half_to_float(txh.value()[2]));
} else if (auto txf = x.get_scalar_value<value::float3>()) {
tx = double(txf.value()[0]);
ty = double(txf.value()[1]);
tz = double(txf.value()[2]);
} else if (auto txd = x.get_scalar_value<value::double3>()) {
tx = txd.value()[0];
ty = txd.value()[1];
tz = txd.value()[2];
} else {
if (err) {
(*err) += "`xformOp:translate` is not half3, float3 or double3 type.\n";
}
return false;
}
if (x.inverted) {
tx = -tx;
ty = -ty;
tz = -tz;
}
m.m[3][0] = tx;
m.m[3][1] = ty;
m.m[3][2] = tz;
break;
}
// FIXME: Validate ROTATE_X, _Y, _Z implementation
case XformOp::OpType::RotateX: {
double angle; // in degrees
if (auto h = x.get_scalar_value<value::half>()) {
angle = double(half_to_float(h.value()));
} else if (auto f = x.get_scalar_value<float>()) {
angle = double(f.value());
} else if (auto d = x.get_scalar_value<double>()) {
angle = d.value();
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:rotateX` is not half, float or double type.\n";
} else {
(*err) += fmt::format("`xformOp:rotateX:{}` is not half, float or double type.\n", x.suffix);
}
}
return false;
}
XformEvaluator xe;
xe.RotateX(angle);
auto ret = xe.result();
if (ret) {
m = ret.value();
} else {
if (err) {
(*err) += ret.error();
}
return false;
}
break;
}
case XformOp::OpType::RotateY: {
double angle; // in degrees
if (auto h = x.get_scalar_value<value::half>()) {
angle = double(half_to_float(h.value()));
} else if (auto f = x.get_scalar_value<float>()) {
angle = double(f.value());
} else if (auto d = x.get_scalar_value<double>()) {
angle = d.value();
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:rotateY` is not half, float or double type.\n";
} else {
(*err) += fmt::format("`xformOp:rotateY:{}` is not half, float or double type.\n", x.suffix);
}
}
return false;
}
XformEvaluator xe;
xe.RotateY(angle);
auto ret = xe.result();
if (ret) {
m = ret.value();
} else {
if (err) {
(*err) += ret.error();
}
return false;
}
break;
}
case XformOp::OpType::RotateZ: {
double angle; // in degrees
if (auto h = x.get_scalar_value<value::half>()) {
angle = double(half_to_float(h.value()));
} else if (auto f = x.get_scalar_value<float>()) {
angle = double(f.value());
} else if (auto d = x.get_scalar_value<double>()) {
angle = d.value();
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:rotateZ` is not half, float or double type.\n";
} else {
(*err) += fmt::format("`xformOp:rotateZ:{}` is not half, float or double type.\n", x.suffix);
}
}
return false;
}
XformEvaluator xe;
xe.RotateZ(angle);
auto ret = xe.result();
if (ret) {
m = ret.value();
} else {
if (err) {
(*err) += ret.error();
}
return false;
}
break;
}
case XformOp::OpType::Orient: {
// value::quat stores elements in (x, y, z, w)
// linalg::quat also stores elements in (x, y, z, w)
value::matrix3d rm;
if (auto h = x.get_scalar_value<value::quath>()) {
rm = to_matrix3x3(h.value());
} else if (auto f = x.get_scalar_value<value::quatf>()) {
rm = to_matrix3x3(f.value());
} else if (auto d = x.get_scalar_value<value::quatd>()) {
rm = to_matrix3x3(d.value());
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:orient` is not quath, quatf or quatd type.\n";
} else {
(*err) += fmt::format("`xformOp:orient:{}` is not quath, quatf or quatd type.\n", x.suffix);
}
}
return false;
}
// FIXME: invert before getting matrix.
if (x.inverted) {
value::matrix3d inv_rm;
if (invert3x3(rm, inv_rm)) {
} else {
if (err) {
if (x.suffix.empty()) {
(*err) += "`xformOp:orient` is singular and cannot be inverted.\n";
} else {
(*err) += fmt::format("`xformOp:orient:{}` is singular and cannot be inverted.\n", x.suffix);
}
}
}
rm = inv_rm;
}
m = to_matrix(rm, {0.0, 0.0, 0.0});
break;
}
case XformOp::OpType::RotateXYZ:
case XformOp::OpType::RotateXZY:
case XformOp::OpType::RotateYXZ:
case XformOp::OpType::RotateYZX:
case XformOp::OpType::RotateZXY:
case XformOp::OpType::RotateZYX: {
auto ret = RotateABC(x);
if (!ret) {
(*err) += ret.error();
return false;
}
m = ret.value();
}
}
cm = Mult<value::matrix4d, double, 4>(cm, m);
}
(*out_matrix) = cm;
return true;
}
} // namespace tinyusdz

View File

@@ -28,5 +28,67 @@ double determinant3x3(const value::matrix3d &m);
bool invert(const value::matrix4d &m, value::matrix4d &inv_m);
bool invert3x3(const value::matrix3d &m, value::matrix3d &inv_m);
//
// For usdGeom, usdLux
// TODO: Move to `xform.hh`?
//
struct Xformable {
///
/// Evaluate XformOps and output evaluated(concatenated) matrix to `out_matrix`
/// `resetXformStack` become true when xformOps[0] is !resetXformStack!
/// Return error message when failed.
///
bool EvaluateXformOps(value::matrix4d *out_matrix, bool *resetXformStack, std::string *err) const;
///
/// Global = Parent x Local
///
nonstd::expected<value::matrix4d, std::string> GetGlobalMatrix(
const value::matrix4d &parentMatrix) const {
bool resetXformStack{false};
auto m = GetLocalMatrix(&resetXformStack);
if (m) {
if (resetXformStack) {
// Ignore parent's transform
// FIXME: Validate this is the correct way of handling !resetXformStack! op.
return m.value();
} else {
value::matrix4d cm =
Mult<value::matrix4d, double, 4>(parentMatrix, m.value());
return cm;
}
} else {
return nonstd::make_unexpected(m.error());
}
}
///
/// Evaluate xformOps and get local matrix.
///
nonstd::expected<value::matrix4d, std::string> GetLocalMatrix(bool *resetTransformStack = nullptr) const {
if (_dirty) {
value::matrix4d m;
std::string err;
if (EvaluateXformOps(&m, resetTransformStack, &err)) {
_matrix = m;
_dirty = false;
} else {
return nonstd::make_unexpected(err);
}
}
return _matrix;
}
void SetDirty(bool onoff) { _dirty = onoff; }
std::vector<XformOp> xformOps;
mutable bool _dirty{true};
mutable value::matrix4d _matrix; // Matrix of this Xform(local matrix)
};
} // namespace tinyusdz

View File

@@ -5,6 +5,7 @@ set(TEST_SOURCES
unit-prim-types.cc
unit-primvar.cc
unit-value-types.cc
unit-xform.cc
)
if (TINYUSDZ_WITH_PXR_COMPAT_API)

34
tests/unit/unit-common.hh Normal file
View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache 2.0
// Copyrigh 2022 - Present, Light Transport Entertainment Inc.
#pragma once
#include <iostream>
#include <limits>
namespace tinyusdz_test {
template <typename T>
static bool float_equals(T x, T y) {
if (std::fabs(x - y) < std::numeric_limits<T>::epsilon()) {
return true;
}
return false;
}
// Simple function to check if two float array values are same.
// Assume two arrays have same length.
template <typename T>
static bool float_array_equals(const T *a, const T *b, const int n) {
for (int i = 0; i < n; i++) {
if (!float_equals(a[i], b[i])) {
std::cerr << "float diff. a[" << i << "] = " << a[i] << ", b[" << i
<< "] = " << b[i] << std::endl;
return false;
}
}
return true;
}
} // namespace tinyusdz_test

View File

@@ -7,6 +7,7 @@
#include "unit-prim-types.h"
#include "unit-primvar.h"
#include "unit-value-types.h"
#include "unit-xform.h"
#if defined(TINYUSDZ_WITH_PXR_COMPAT_API)
#include "unit-pxr-compat-api.h"
@@ -18,6 +19,7 @@ TEST_LIST = {
{ "prim_type_test", prim_type_test },
{ "primvar_test", primvar_test },
{ "value_types_test", value_types_test },
{ "xformOp_test", xformOp_test },
#if defined(TINYUSDZ_WITH_PXR_COMPAT_API)
{ "pxr_compat_api_test", pxr_compat_api_test },
#endif

45
tests/unit/unit-xform.cc Normal file
View File

@@ -0,0 +1,45 @@
#ifdef _MSC_VER
#define NOMINMAX
#endif
#define TEST_NO_MAIN
#include "acutest.h"
#include "unit-value-types.h"
#include "prim-types.hh"
#include "xform.hh"
#include "unit-common.hh"
using namespace tinyusdz;
using namespace tinyusdz_test;
void xformOp_test(void) {
{
value::double3 scale = {1.0, 2.0, 3.0};
XformOp op;
op.op = XformOp::OpType::Scale;
op.inverted = true;
op.set_scalar(scale);
Xformable x;
x.xformOps.push_back(op);
value::matrix4d m;
bool resetXformStack;
std::string err;
bool ret = x.EvaluateXformOps(&m, &resetXformStack, &err);
TEST_CHECK(ret == true);
TEST_CHECK(float_equals(m.m[0][0], 1.0));
TEST_CHECK(float_equals(m.m[1][1], 1.0/2.0));
TEST_CHECK(float_equals(m.m[2][2], 1.0/3.0));
}
}

3
tests/unit/unit-xform.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
void xformOp_test(void);

View File

@@ -0,0 +1,20 @@
#usda 1.0
(
defaultPrim = "Marble"
)
def Xform "Marble" (
kind = "component"
)
{
def Sphere "marble_geom"
{
rel material:binding = [</Marble/GlassMaterial>, </bora>,]
color3f[] primvars:displayColor = [ (0, 1, 0) ]
}
def Material "GlassMaterial"
{
# Interface inputs, shading networks, etc.
}
}

View File

@@ -10,7 +10,7 @@ def Xform "Marble" (
def Sphere "marble_geom"
{
# Ok to have ',' in last item
rel material:binding = [</Marble/GlassMaterial>, </bora>,]
rel myrels = [</Marble/GlassMaterial>, </bora>,]
color3f[] primvars:displayColor = [ (0, 1, 0) ]
}

View File

@@ -1,8 +0,0 @@
#usda 1.0
def Xform "muda"
{
float xformOp:rotateZ:tilt = 12
uniform token[] xformOpOrder = ["xformOp:rotateZ:tilt"]
}