///|
/// Discrete constraint solver for a sampled scalar motion signal. It performs
/// forward and backward passes so an edit can honour limits without changing
/// the number of frames requested by the renderer.
pub struct ConstraintSolveConfig {
frame_rate : Double
limits : MotionLimitProfile2
preserve_endpoints : Bool
passes : Int
} derive(Debug)
///|
pub fn constraint_solve_config(
frame_rate : Double,
limits : MotionLimitProfile2,
) -> ConstraintSolveConfig {
{
frame_rate: frame_rate.max(0.001),
limits,
preserve_endpoints: true,
passes: 3,
}
}
///|
pub fn ConstraintSolveConfig::with_endpoints(
self : ConstraintSolveConfig,
preserve : Bool,
) -> ConstraintSolveConfig {
{
frame_rate: self.frame_rate,
limits: self.limits,
preserve_endpoints: preserve,
passes: self.passes,
}
}
///|
pub fn ConstraintSolveConfig::with_passes(
self : ConstraintSolveConfig,
passes : Int,
) -> ConstraintSolveConfig {
{
frame_rate: self.frame_rate,
limits: self.limits,
preserve_endpoints: self.preserve_endpoints,
passes: passes.max(1).min(32),
}
}
///|
pub fn ConstraintSolveConfig::frame_rate(
self : ConstraintSolveConfig,
) -> Double {
self.frame_rate
}
///|
pub fn ConstraintSolveConfig::limits(
self : ConstraintSolveConfig,
) -> MotionLimitProfile2 {
self.limits
}
///|
pub struct ConstraintSolveResult {
values : Array[Double]
velocities : Array[Double]
accelerations : Array[Double]
maximum_error : Double
iterations : Int
converged : Bool
} derive(Debug)
///|
pub fn ConstraintSolveResult::values(
self : ConstraintSolveResult,
) -> Array[Double] {
self.values.copy()
}
///|
pub fn ConstraintSolveResult::velocities(
self : ConstraintSolveResult,
) -> Array[Double] {
self.velocities.copy()
}
///|
pub fn ConstraintSolveResult::accelerations(
self : ConstraintSolveResult,
) -> Array[Double] {
self.accelerations.copy()
}
///|
pub fn ConstraintSolveResult::maximum_error(
self : ConstraintSolveResult,
) -> Double {
self.maximum_error
}
///|
pub fn ConstraintSolveResult::iterations(self : ConstraintSolveResult) -> Int {
self.iterations
}
///|
pub fn ConstraintSolveResult::converged(self : ConstraintSolveResult) -> Bool {
self.converged
}
///|
fn solve_pass(
values : Array[Double],
target : Array[Double],
config : ConstraintSolveConfig,
forward : Bool,
) -> Double {
if values.length() < 2 {
return 0.0
}
let dt = 1.0 / config.frame_rate
let mut maximum_error = 0.0
if forward {
for index in 1.. 1 { values[index - 2] } else { previous }
let result = constrain_scalar_step(
target[index],
previous,
(previous - prior) / dt,
0.0,
dt,
config.limits,
)
let error = (values[index] - result.value).abs()
maximum_error = maximum_error.max(error)
values[index] = result.value
}
} else {
let mut index = values.length() - 2
while index >= 0 {
let next = values[index + 1]
let after = if index + 2 < values.length() {
values[index + 2]
} else {
next
}
let result = constrain_scalar_step(
target[index],
next,
(next - after) / dt,
0.0,
dt,
config.limits,
)
let error = (values[index] - result.value).abs()
maximum_error = maximum_error.max(error)
values[index] = result.value
index = index - 1
}
}
maximum_error
}
///|
pub fn solve_constrained_signal(
values : Array[Double],
config : ConstraintSolveConfig,
) -> ConstraintSolveResult {
if values.length() == 0 {
return {
values: [],
velocities: [],
accelerations: [],
maximum_error: 0.0,
iterations: 0,
converged: true,
}
}
let target = values.copy()
let solved = values.copy()
let mut maximum_error = 0.0
let mut iteration = 0
let mut converged = false
while iteration < config.passes && !converged {
let forward_error = solve_pass(solved, target, config, true)
let backward_error = solve_pass(solved, target, config, false)
maximum_error = forward_error.max(backward_error)
if config.preserve_endpoints {
solved[0] = target[0]
solved[solved.length() - 1] = target[target.length() - 1]
}
converged = maximum_error <= config.limits.tolerance
iteration = iteration + 1
}
let dt = 1.0 / config.frame_rate
let velocities : Array[Double] = []
let accelerations : Array[Double] = []
for index in 0.. Array[Point2] {
let x : Array[Double] = []
let y : Array[Double] = []
for point in values {
x.push(point.x())
y.push(point.y())
}
let solved_x = solve_constrained_signal(x, config).values
let solved_y = solve_constrained_signal(y, config).values
let result : Array[Point2] = []
for index in 0.. Array[Point3] {
let x : Array[Double] = []
let y : Array[Double] = []
let z : Array[Double] = []
for point in values {
x.push(point.x())
y.push(point.y())
z.push(point.z())
}
let solved_x = solve_constrained_signal(x, config).values
let solved_y = solve_constrained_signal(y, config).values
let solved_z = solve_constrained_signal(z, config).values
let result : Array[Point3] = []
for index in 0.. Int {
self.removed
}
///|
pub fn KeyframeCleanupReport::merged(self : KeyframeCleanupReport) -> Int {
self.merged
}
///|
pub fn KeyframeCleanupReport::adjusted(self : KeyframeCleanupReport) -> Int {
self.adjusted
}
///|
pub fn KeyframeCleanupReport::maximum_adjustment(
self : KeyframeCleanupReport,
) -> Double {
self.maximum_adjustment
}
///|
pub fn cleanup_samples(
values : Array[Double],
tolerance : Double,
) -> (Array[Double], KeyframeCleanupReport) {
if values.length() < 3 {
return (
values.copy(),
{ removed: 0, merged: 0, adjusted: 0, maximum_adjustment: 0.0 },
)
}
let result : Array[Double] = [values[0]]
let mut removed = 0
let mut merged = 0
for index in 1.. Array[Double] {
let result = values.copy()
if result.length() < 2 {
return result
}
if increasing {
for index in 1.. Array[Double] {
let result : Array[Double] = []
for value in values {
result.push(clamp(value, minimum, maximum))
}
result
}
///|
pub fn resample_constrained_signal(
values : Array[Double],
count : Int,
) -> Array[Double] {
if values.length() == 0 {
return []
}
let result : Array[Double] = []
let actual = count.max(1)
for index in 0..