mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 05:27:08 -06:00
#1010: leave keyed Orientation authored under a tilted parent
Euler angles are blended between keys, so re-expressing only the key values cannot keep the motion between them under a tilted parent. Keyed Orientation is now re-expressed only for a Z-only correction, which shifts Z exactly at every time; static Orientation is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
707161bc63
commit
95f2f3ec55
@@ -789,9 +789,10 @@ fn reexpress_euler(correction: effectcraft_geom::Mat4, o: [f64; 3], reference: [
|
||||
nearest_euler(euler_zyx(world.0), reference)
|
||||
}
|
||||
|
||||
/// Re-expresses Orientation (the value and every key) as `correction * Orientation`. A Z-only
|
||||
/// correction shifts Z exactly, so the keys and the paths between them stay exact. Other
|
||||
/// corrections keep each key's authored turns, so the Euler values stay continuous.
|
||||
/// Re-expresses Orientation as `correction * Orientation`. A Z-only correction shifts Z exactly in the
|
||||
/// value and every key, so the paths between keys stay exact too. Any other correction is applied
|
||||
/// only to a static value (`parent_fixes` leaves keyed Orientation alone then): Euler angles are
|
||||
/// blended between keys, which no re-expression of the keys can reproduce.
|
||||
fn reexpress_orientation(correction: effectcraft_geom::Mat4, pr: &mut effectcraft_project::Property) {
|
||||
if let Some(turn) = z_only_turn(correction) {
|
||||
// No turn (an identity correction) leaves every authored value as it is.
|
||||
@@ -810,18 +811,6 @@ fn reexpress_orientation(correction: effectcraft_geom::Mat4, pr: &mut effectcraf
|
||||
}
|
||||
let value = pr.value.as_vec3();
|
||||
pr.value = KV::Vec3(reexpress_euler(correction, value, value));
|
||||
let mut prev: Option<([f64; 3], [f64; 3])> = None;
|
||||
for key in &mut pr.keys {
|
||||
let o = key.value.as_vec3();
|
||||
// Each key keeps the authored change from the previous key on top of its converted value.
|
||||
let reference = match prev {
|
||||
Some((before, after)) => [after[0] + o[0] - before[0], after[1] + o[1] - before[1], after[2] + o[2] - before[2]],
|
||||
None => o,
|
||||
};
|
||||
let n = reexpress_euler(correction, o, reference);
|
||||
key.value = KV::Vec3(n);
|
||||
prev = Some((o, n));
|
||||
}
|
||||
}
|
||||
|
||||
/// Correction for a two-node rig's Orientation: after a uniform parent change its aim is
|
||||
@@ -923,6 +912,10 @@ pub(crate) fn parent_fixes(s: &Session, cid: ItemId, ids: &[LayerId], parent: Op
|
||||
if !correction.0.iter().flatten().all(|v| v.is_finite()) || !orientation_finite {
|
||||
return None;
|
||||
}
|
||||
// Keys can only follow a Z-only correction exactly; otherwise they stay authored.
|
||||
if !orientation.keys.is_empty() && z_only_turn(correction).is_none() {
|
||||
return None;
|
||||
}
|
||||
if let Some(scale) = scale {
|
||||
let values_finite = std::iter::once(&scale.value)
|
||||
.chain(scale.keys.iter().map(|key| &key.value))
|
||||
|
||||
@@ -282,22 +282,38 @@ fn parented_point_light_keeps_its_direction_under_a_rotated_null() {
|
||||
assert!((eye1 - eye0).length() < 1e-6 && (f1 - f0).length() < 1e-6, "{eye0:?} {eye1:?} {f0:?} {f1:?}");
|
||||
}
|
||||
|
||||
/// Keyed Orientation is re-expressed at each key under a rotated null, so the light keeps its
|
||||
/// direction at every key.
|
||||
/// Keyed Orientation under a Z-only turn is shifted exactly, so the light keeps its direction at
|
||||
/// every time, between the keys too.
|
||||
#[test]
|
||||
fn keyed_orientation_is_re_expressed_at_each_key_under_a_rotated_null() {
|
||||
fn keyed_orientation_under_a_z_turn_keeps_the_direction_at_every_time() {
|
||||
let mut s = session();
|
||||
let light = s.execute("layer.newLight", json!({"kind": "Point", "position": [320, 180, -500]})).unwrap()["layer"].as_u64().unwrap();
|
||||
for (time, value) in [(0.0, json!([-8.0, 12.0, -13.0])), (1.0, json!([5.0, 18.0, 14.0]))] {
|
||||
s.execute("prop.addKey", json!({"layer": light, "path": "transform/orientation", "time": time, "value": value})).unwrap();
|
||||
}
|
||||
let keys = [Tick::ZERO, Tick::from_seconds_f64(1.0)];
|
||||
let before = keys.map(|t| frame_at(&s, light, t));
|
||||
let times = [0.0, 0.25, 0.5, 0.75, 1.0].map(Tick::from_seconds_f64);
|
||||
let before = times.map(|t| frame_at(&s, light, t));
|
||||
let nul = rotated_null(&mut s, [0.0, 0.0, 0.0], 30.0);
|
||||
s.execute("layer.setParent", json!({"layers": [light], "parent": nul})).unwrap();
|
||||
for (t, b) in times.into_iter().zip(before) {
|
||||
assert!(frames_match(b, frame_at(&s, light, t)), "a Z-only turn keeps the light's direction");
|
||||
}
|
||||
}
|
||||
|
||||
/// Under a tilted null the keys cannot follow exactly (Euler angles are blended between them), so
|
||||
/// keyed Orientation stays as authored rather than changing the motion between the keys.
|
||||
#[test]
|
||||
fn keyed_orientation_under_a_tilted_null_stays_authored() {
|
||||
let mut s = session();
|
||||
let light = s.execute("layer.newLight", json!({"kind": "Point", "position": [320, 180, -500]})).unwrap()["layer"].as_u64().unwrap();
|
||||
for (time, value) in [(0.0, json!([-8.0, 12.0, -13.0])), (1.0, json!([5.0, 18.0, 14.0]))] {
|
||||
s.execute("prop.addKey", json!({"layer": light, "path": "transform/orientation", "time": time, "value": value})).unwrap();
|
||||
}
|
||||
let keys_before = layer_in(&s, cid(&s), light).props.prop("transform/orientation").unwrap().keys.iter().map(|k| k.value.as_vec3()).collect::<Vec<_>>();
|
||||
let nul = rotated_null(&mut s, [20.0, -35.0, 60.0], 30.0);
|
||||
s.execute("layer.setParent", json!({"layers": [light], "parent": nul})).unwrap();
|
||||
for (t, b) in keys.into_iter().zip(before) {
|
||||
assert!(frames_match(b, frame_at(&s, light, t)), "keyed Orientation keeps the light's direction at each key");
|
||||
}
|
||||
let keys_after = layer_in(&s, cid(&s), light).props.prop("transform/orientation").unwrap().keys.iter().map(|k| k.value.as_vec3()).collect::<Vec<_>>();
|
||||
assert_eq!(keys_before, keys_after);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- from 3D model
|
||||
|
||||
Reference in New Issue
Block a user