182 lines
6.6 KiB
C++
Raw Normal View History

2021-11-11 21:22:24 +01:00
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/runtime/track_triggering_job.h"
#include "ozz/animation/runtime/track.h"
#include <algorithm>
#include <cassert>
namespace ozz {
namespace animation {
TrackTriggeringJob::TrackTriggeringJob()
: from(0.f), to(0.f), threshold(0.f), track(nullptr), iterator(nullptr) {}
bool TrackTriggeringJob::Validate() const {
bool valid = true;
valid &= track != nullptr;
valid &= iterator != nullptr;
return valid;
}
bool TrackTriggeringJob::Run() const {
if (!Validate()) {
return false;
}
// Triggering can only happen in a valid range of ratio.
if (from == to) {
*iterator = end();
return true;
}
*iterator = Iterator(this);
return true;
}
namespace {
inline bool DetectEdge(ptrdiff_t _i0, ptrdiff_t _i1, bool _forward,
const TrackTriggeringJob& _job,
TrackTriggeringJob::Edge* _edge) {
const span<const float>& values = _job.track->values();
const float vk0 = values[_i0];
const float vk1 = values[_i1];
bool detected = false;
if (vk0 <= _job.threshold && vk1 > _job.threshold) {
// Rising edge
_edge->rising = _forward;
detected = true;
} else if (vk0 > _job.threshold && vk1 <= _job.threshold) {
// Falling edge
_edge->rising = !_forward;
detected = true;
}
if (detected) {
const span<const float>& ratios = _job.track->ratios();
const span<const uint8_t>& steps = _job.track->steps();
const bool step = (steps[_i0 / 8] & (1 << (_i0 & 7))) != 0;
if (step) {
_edge->ratio = ratios[_i1];
} else {
assert(vk0 != vk1); // Won't divide by 0
if (_i1 == 0) {
_edge->ratio = 0.f;
} else {
// Finds where the curve crosses threshold value.
// This is the lerp equation, where we know the result and look for
// alpha, aka un-lerp.
const float alpha = (_job.threshold - vk0) / (vk1 - vk0);
// Remaps to keyframes actual times.
const float tk0 = ratios[_i0];
const float tk1 = ratios[_i1];
_edge->ratio = math::Lerp(tk0, tk1, alpha);
}
}
}
return detected;
}
} // namespace
TrackTriggeringJob::Iterator::Iterator(const TrackTriggeringJob* _job)
: job_(_job) {
// Outer loop initialization.
outer_ = floorf(job_->from);
// Search could start more closely to the "from" ratio, but it's not possible
// to ensure that floating point precision will not lead to missing a key
// (when from/to range is far from 0). This is less good in algorithmic
// complexity, but for consistency of forward and backward triggering, it's
// better to let iterator ++ implementation filter included and excluded
// edges.
inner_ = job_->from < job_->to ? 0 : _job->track->ratios().size() - 1;
// Evaluates first edge.
++*this;
}
const TrackTriggeringJob::Iterator& TrackTriggeringJob::Iterator::operator++() {
assert(*this != job_->end() && "Can't increment end iterator.");
const span<const float>& ratios = job_->track->ratios();
const ptrdiff_t num_keys = ratios.size();
if (job_->to > job_->from) {
for (; outer_ < job_->to; outer_ += 1.f) {
for (; inner_ < num_keys; ++inner_) {
const ptrdiff_t i0 = inner_ == 0 ? num_keys - 1 : inner_ - 1;
if (DetectEdge(i0, inner_, true, *job_, &edge_)) {
edge_.ratio += outer_; // Convert to global ratio space.
if (edge_.ratio >= job_->from &&
(edge_.ratio < job_->to || job_->to >= 1.f + outer_)) {
++inner_;
return *this; // Yield found edge.
}
// Won't find any further edge.
if (ratios[inner_] + outer_ >= job_->to) {
break;
}
}
}
inner_ = 0; // Ready for next loop.
}
} else {
for (; outer_ + 1.f > job_->to; outer_ -= 1.f) {
for (; inner_ >= 0; --inner_) {
const ptrdiff_t i0 = inner_ == 0 ? num_keys - 1 : inner_ - 1;
if (DetectEdge(i0, inner_, false, *job_, &edge_)) {
edge_.ratio += outer_; // Convert to global ratio space.
if (edge_.ratio >= job_->to &&
(edge_.ratio < job_->from || job_->from >= 1.f + outer_)) {
--inner_;
return *this; // Yield found edge.
}
}
// Won't find any further edge.
if (ratios[inner_] + outer_ <= job_->to) {
break;
}
}
inner_ = ratios.size() - 1; // Ready for next loop.
}
}
// Set iterator to end position.
*this = job_->end();
return *this;
}
} // namespace animation
} // namespace ozz