mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Refactor Xform.
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
531
src/usdGeom.cc
531
src/usdGeom.cc
@@ -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
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "prim-types.hh"
|
||||
#include "value-types.hh"
|
||||
#include "xform.hh"
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
496
src/xform.cc
496
src/xform.cc
@@ -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
|
||||
|
||||
62
src/xform.hh
62
src/xform.hh
@@ -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
|
||||
|
||||
@@ -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
34
tests/unit/unit-common.hh
Normal 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
|
||||
@@ -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
45
tests/unit/unit-xform.cc
Normal 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
3
tests/unit/unit-xform.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void xformOp_test(void);
|
||||
20
tests/usda/fail-case/material-binding-multiple-rels-000.usda
Normal file
20
tests/usda/fail-case/material-binding-multiple-rels-000.usda
Normal 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.
|
||||
}
|
||||
}
|
||||
@@ -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) ]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#usda 1.0
|
||||
|
||||
def Xform "muda"
|
||||
{
|
||||
float xformOp:rotateZ:tilt = 12
|
||||
|
||||
uniform token[] xformOpOrder = ["xformOp:rotateZ:tilt"]
|
||||
}
|
||||
Reference in New Issue
Block a user