Skip to content

Commit

Permalink
[Tydra] Animation support W.I.P.
Browse files Browse the repository at this point in the history
  • Loading branch information
syoyo committed Apr 18, 2024
1 parent 01b565a commit 1161aef
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 11 deletions.
170 changes: 167 additions & 3 deletions src/tydra/render-data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5120,15 +5120,179 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e
const Path &abs_path,
const SkelAnimation &skelAnim,
Animation *anim_out) {
// The spec says
// """
// An animation source is only valid if its translation, rotation, and scale components are all authored, storing arrays size to the same size as the authored joints array.
// """

// NOTE: fortunately USD SkelAnimation uses quaternions for rotations
// anim_out->channels.rotations

(void)env;
(void)abs_path;
(void)skelAnim;
(void)anim_out;

AnimationChannel channel_txs; channel_txs.type = AnimationChannel::ChannelType::Translation;
AnimationChannel channel_rots; channel_rots.type = AnimationChannel::ChannelType::Rotation;
AnimationChannel channel_scales; channel_scales.type = AnimationChannel::ChannelType::Scale;

if (!skelAnim.joints.authored()) {
PUSH_ERROR_AND_RETURN(fmt::format("`joints` is not authored for SkelAnimation Prim : {}", abs_path));
}

std::vector<value::token> joints;
if (!EvaluateTypedAttribute(env.stage, skelAnim.joints, "joints", &joints, &_err)) {
PUSH_ERROR_AND_RETURN(fmt::format("Failed to evaluate `joints` in SkelAnimation Prim : {}", abs_path));
}


if (!skelAnim.rotations.authored() ||
!skelAnim.translations.authored() ||
!skelAnim.scales.authored()) {

PUSH_ERROR_AND_RETURN(fmt::format("`translations`, `rotations` and `scales` must be all authored for SkelAnimation Prim {}. authored flags: translations {}, rotations {}, scales {}", abs_path, skelAnim.translations.authored() ? "yes" : "no",
skelAnim.rotations.authored() ? "yes" : "no",
skelAnim.scales.authored() ? "yes" : "no"));
}


Animatable<std::vector<value::float3>> translations;
if (!skelAnim.translations.get_value(&translations)) {
PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path));
}

Animatable<std::vector<value::quatf>> rotations;
if (!skelAnim.rotations.get_value(&rotations)) {
PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path));
}

Animatable<std::vector<value::half3>> scales;
if (!skelAnim.scales.get_value(&scales)) {
PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path));
}

bool is_translations_timesamples = false;
bool is_rotations_timesamples = false;
bool is_scales_timesamples = false;

if (translations.is_timesamples()) {
const TypedTimeSamples<std::vector<value::float3>> &ts_txs = translations.get_timesamples();

if (ts_txs.get_samples().empty()) {
PUSH_ERROR_AND_RETURN(fmt::format("`translations` timeSamples in SkelAnimation is empty : {}", abs_path));
}

for (const auto &sample : ts_txs.get_samples()) {
AnimationSample<std::vector<value::float3>> dst;
if (!sample.blocked) {
// length check
if (sample.value.size() != joints.size()) {
PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} translations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path));
}

dst.t = float(sample.t);
dst.value = sample.value;
channel_txs.translations.samples.push_back(dst);
}
}
is_translations_timesamples = true;
}

const TypedTimeSamples<std::vector<value::quatf>> &ts_rots = rotations.get_timesamples();
for (const auto &sample : ts_rots.get_samples()) {
if (!sample.blocked) {
if (sample.value.size() != joints.size()) {
PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} rotations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path));
}
}
}

const TypedTimeSamples<std::vector<value::half3>> &ts_scales = scales.get_timesamples();
for (const auto &sample : ts_scales.get_samples()) {
if (!sample.blocked) {
if (sample.value.size() != joints.size()) {
PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} scales.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path));
}
}
}

std::vector<value::float3> translation;
std::vector<value::float4> rotation;
std::vector<value::float3> scale;

// Get value and also do length check for scalar(non timeSampled) animation value.
if (translations.is_scalar()) {
if (!translations.get_scalar(&translation)) {
PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute in SkelAnimation: {}", abs_path));
}
if (translation.size() != joints.size()) {
PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. translations.default.size {} must be equal to joints.size {} : {}", translation.size(), joints.size(), abs_path));
}
is_translations_timesamples = false;
}

if (rotations.is_scalar()) {
std::vector<value::quatf> _rotation;
if (!rotations.get_scalar(&_rotation)) {
PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute in SkelAnimation: {}", abs_path));
}
if (_rotation.size() != joints.size()) {
PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. rotations.default.size {} must be equal to joints.size {} : {}", _rotation.size(), joints.size(), abs_path));
}
std::transform(_rotation.begin(), _rotation.end(), std::back_inserter(rotation), [](const value::quatf &v) {
value::float4 ret;
// pxrUSD's TfQuat also uses xyzw memory order.
ret[0] = v[0];
ret[1] = v[1];
ret[2] = v[2];
ret[3] = v[3];
return ret;
});
is_rotations_timesamples = false;
}

if (scales.is_scalar()) {
std::vector<value::half3> _scale;
if (!scales.get_scalar(&_scale)) {
PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute in SkelAnimation: {}", abs_path));
}
if (_scale.size() != joints.size()) {
PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. scale.default.size {} must be equal to joints.size {} : {}", _scale.size(), joints.size(), abs_path));
}
// half -> float
std::transform(_scale.begin(), _scale.end(), std::back_inserter(scale), [](const value::half3 &v) {
value::float3 ret;
ret[0] = value::half_to_float(v[0]);
ret[1] = value::half_to_float(v[1]);
ret[2] = value::half_to_float(v[2]);
return ret;
});
is_scales_timesamples = false;
}

// Use USD TimeCode::Default for static sample.
if (is_translations_timesamples) {
} else {
AnimationSample<std::vector<value::float3>> sample;
sample.t = std::numeric_limits<float>::quiet_NaN();
sample.value = translation;
channel_txs.translations.samples.push_back(sample);
}

if (is_rotations_timesamples) {
} else {
AnimationSample<std::vector<value::float4>> sample;
sample.t = std::numeric_limits<float>::quiet_NaN();
sample.value = rotation;
channel_rots.rotations.samples.push_back(sample);
}

if (is_scales_timesamples) {
} else {
AnimationSample<std::vector<value::float3>> sample;
sample.t = std::numeric_limits<float>::quiet_NaN();
sample.value = scale;
channel_scales.scales.samples.push_back(sample);
}

PUSH_ERROR_AND_RETURN("TODO");

}
Expand Down
34 changes: 29 additions & 5 deletions src/tydra/render-data.hh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ namespace tydra {
using vec2 = value::float2;
using vec3 = value::float3;
using vec4 = value::float4;
using quat = value::float4;
using quat = value::float4; // (x, y, z, w)
using mat2 = value::matrix2f;
using mat3 = value::matrix3f;
using mat4 = value::matrix4f;
Expand Down Expand Up @@ -642,11 +642,15 @@ struct EnvmapLight

// glTF-lie animation data

// TOOD: Implement Animation sample resampler.

// In USD, timeSamples are linearly interpolated by default.
// For default value(value at static time), create a Sample at time `-inf`(USD TimeCode::Default)
template <typename T>
struct AnimationSampler {
std::vector<AnimationSample<T>> samples;

// No cubicSpline
// No cubicSpline in USD
enum class Interpolation {
Linear,
Step, // Held in USD
Expand All @@ -659,15 +663,18 @@ struct AnimationSampler {
struct AnimationChannel {
enum class ChannelType { Transform, Translation, Rotation, Scale, Weight };

ChannelType type;
// The following AnimationSampler is filled depending on ChannelType.
// Example: Rotation => Only `rotations` are filled.

// Matrix precision is reduced to float-precision
// NOTE: transform is not supported in glTF(you need to decompose transform
// matrix into TRS)
AnimationSampler<std::vector<mat4>> transforms;

// half-types are upcasted to float precision
AnimationSampler<std::vector<vec3>> translations;
AnimationSampler<std::vector<quat>> rotations; // Rotation is represented as quaternions
AnimationSampler<std::vector<vec3>> scales;
AnimationSampler<std::vector<vec3>> scales; // half-types are upcasted to float precision
AnimationSampler<std::vector<float>> weights;

int64_t taget_node{-1}; // array index to RenderScene::nodes
Expand Down Expand Up @@ -1138,12 +1145,29 @@ struct RenderLight
// TODO..
};

struct SceneMetadata
{
std::string copyright;
std::string comment;

std::string upAxis{"Y"}; // "X", "Y" or "Z"
nonstd::optional<double> startTimeCode;
nonstd::optional<double> endTimeCode;
double framesPerSecond{24.0};
double timeCodesPerSecond{24.0};
double metersPerUnit{1.0}; // default [m]

bool autoPlay{true};

// If you want to lookup more thing on USD Stage Metadata, Use Stage::metas()
};

// Simple glTF-like Scene Graph
class RenderScene {
public:
std::string usd_filename;

std::string upAxis{"Y"}; // "X", "Y" or "Z"
SceneMetadata meta;

uint32_t default_root_node{0}; // index to `nodes`.

Expand Down
6 changes: 3 additions & 3 deletions src/tydra/usd-export.cc
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,11 @@ bool export_to_usda(const RenderScene &scene,
Stage stage;

stage.metas().comment = "Exported from TinyUSDZ Tydra.";
if (scene.upAxis == "X") {
if (scene.meta.upAxis == "X") {
stage.metas().upAxis = Axis::X;
} else if (scene.upAxis == "Y") {
} else if (scene.meta.upAxis == "Y") {
stage.metas().upAxis = Axis::Y;
} else if (scene.upAxis == "Z") {
} else if (scene.meta.upAxis == "Z") {
stage.metas().upAxis = Axis::Z;
}

Expand Down

0 comments on commit 1161aef

Please sign in to comment.