Merge pull request #1038 from vbasky/fix/1010-camera-parent
This commit is contained in:
commit
69f416e3b0
|
|
@ -647,6 +647,28 @@ impl ParentFix {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Cameras and lights aim with Point of Interest: move it by the same parent-space
|
||||
// change, so a two-node rig keeps its world frame (#1010, #1020).
|
||||
if let Some(pr) = tr.get_mut("poi") {
|
||||
pr.value = map_position(&pr.value);
|
||||
for key in &mut pr.keys {
|
||||
key.value = map_position(&key.value);
|
||||
for tangent in [&mut key.spatial_in, &mut key.spatial_out] {
|
||||
let v = m.apply_vec(effectcraft_geom::Vec3::from(*tangent));
|
||||
*tangent = [v.x, v.y, v.z];
|
||||
}
|
||||
}
|
||||
if let Some(factor) = position_speed
|
||||
.filter(|_| pr.spatial)
|
||||
.filter(|factor| pr.keys.iter().flat_map(|key| key.in_ease.iter().chain(&key.out_ease)).all(|ease| (ease.speed * *factor).is_finite()))
|
||||
{
|
||||
for key in &mut pr.keys {
|
||||
for ease in key.in_ease.iter_mut().chain(&mut key.out_ease) {
|
||||
ease.speed *= factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((orientation, factor)) = self.pose_3d {
|
||||
if let Some(pr) = tr.get_mut("orientation") {
|
||||
pr.value = KV::Vec3(orientation);
|
||||
|
|
@ -749,7 +771,8 @@ fn uniform_parent_pose(m: effectcraft_geom::Mat4, orientation: [f64; 3]) -> Opti
|
|||
Some(([x.to_degrees(), y.to_degrees(), z.to_degrees()], factor))
|
||||
}
|
||||
|
||||
/// A parent change retains 2D transforms and the world-space anchor of 3D AV layers. Position is
|
||||
/// A parent change retains 2D transforms and the world-space anchor of 3D layers (Position,
|
||||
/// and Point of Interest for two-node cameras and lights). Position is
|
||||
/// re-expressed in the new parent's space (`None`: the composition), evaluated at the current
|
||||
/// time.
|
||||
pub(crate) fn parent_fixes(s: &Session, cid: ItemId, ids: &[LayerId], parent: Option<LayerId>) -> Vec<ParentFix> {
|
||||
|
|
@ -793,7 +816,10 @@ pub(crate) fn parent_fixes(s: &Session, cid: ItemId, ids: &[LayerId], parent: Op
|
|||
})
|
||||
};
|
||||
if let Some(inv) = space_3d(parent).inverse().filter(|m| m.0.iter().flatten().all(|v| v.is_finite())) {
|
||||
for layer in comp.layers.iter().filter(|l| ids.contains(&l.id) && l.is_3d() && !l.is_camera() && !l.is_light() && l.parent != parent) {
|
||||
// Cameras and lights compensate like 3D layers (Position, and Point of Interest for
|
||||
// two-node rigs), so parenting keeps the world frame (#1010, #1020). They have no
|
||||
// Scale, so `pose_3d` below stays `None` and Orientation is kept as authored.
|
||||
for layer in comp.layers.iter().filter(|l| ids.contains(&l.id) && l.is_3d() && l.parent != parent) {
|
||||
let m = inv * space_3d(layer.parent);
|
||||
if m.0.iter().flatten().all(|v| v.is_finite()) {
|
||||
let pose_3d = layer.transform().and_then(|tr| {
|
||||
|
|
|
|||
|
|
@ -462,3 +462,41 @@ fn parenting_uniform_parent_preserves_eased_position_at_quarter_times_and_undo()
|
|||
}
|
||||
assert!(maximum_error < 1e-8, "3D uniform parent compensation must preserve eased Position at quarter and three-quarter times");
|
||||
}
|
||||
|
||||
/// Parenting a two-node camera to a default null keeps its world frame (eye, forward, down):
|
||||
/// Position and Point of Interest are re-expressed in the null's space (#1010, #1020).
|
||||
#[test]
|
||||
fn parenting_camera_to_default_null_preserves_framing() {
|
||||
use effectcraft_engine::render::three_d::camera::layer_frame;
|
||||
let mut s = Session::default();
|
||||
command(&mut s, "comp.new", json!({"name":"Camera","width":640,"height":360,"duration":2,"frameRate":24}));
|
||||
let cam = command(&mut s, "layer.newCamera", json!({"name":"Camera 1"}))["layer"].as_u64().unwrap();
|
||||
let null = command(&mut s, "layer.newNull", json!({"name":"Null 1"}))["layer"].as_u64().unwrap();
|
||||
let frame = |s: &Session| {
|
||||
let cid = s.active_comp_id().unwrap();
|
||||
let c = s.project.comp(cid).unwrap();
|
||||
let ctx = EvalCtx::new(&s.project, cid, c, Tick::ZERO);
|
||||
layer_frame(&ctx, c.layer(LayerId(cam)).unwrap())
|
||||
};
|
||||
let authored = |s: &Session, path: &str| {
|
||||
let cid = s.active_comp_id().unwrap();
|
||||
s.project.comp(cid).unwrap().layer(LayerId(cam)).unwrap().props.prop(path).unwrap().value.as_vec3()
|
||||
};
|
||||
let (eye0, fwd0, down0) = frame(&s);
|
||||
let (pos0, poi0) = (authored(&s, "transform/position"), authored(&s, "transform/poi"));
|
||||
let frame_error = |(a0, b0, c0): (Vec3, Vec3, Vec3), (a1, b1, c1): (Vec3, Vec3, Vec3)| {
|
||||
[(a0 - a1).length(), (b0 - b1).length(), (c0 - c1).length()].into_iter().fold(0.0, f64::max)
|
||||
};
|
||||
let undo = s.history.undo.len();
|
||||
parent(&mut s, cam, Some(null), true);
|
||||
assert!(frame_error(frame(&s), (eye0, fwd0, down0)) < 1e-9, "the camera frame must not move");
|
||||
// Compensation rewrote the authored values, not just the parent pointer.
|
||||
assert!((authored(&s, "transform/position")[0] - pos0[0]).abs() > 1.0, "position is re-expressed in the null's space");
|
||||
assert!((authored(&s, "transform/poi")[0] - poi0[0]).abs() > 1.0, "point of interest follows it");
|
||||
assert_eq!(s.history.undo.len(), undo + 1, "parent compensation is one undo action");
|
||||
// Unparenting restores the authored transform and the same frame.
|
||||
parent(&mut s, cam, None, true);
|
||||
assert!(frame_error(frame(&s), (eye0, fwd0, down0)) < 1e-9);
|
||||
assert_eq!(authored(&s, "transform/position"), pos0, "unparenting restores position");
|
||||
assert_eq!(authored(&s, "transform/poi"), poi0, "unparenting restores point of interest");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue