mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 05:27:08 -06:00
#1010: keep a parented camera's or light's Point of Interest in place
Parenting a camera or light (layer.setParent, compensate on) now re-expresses its Position and Point of Interest in the new parent's space, like the other 3D layers. The 3D loop in parent_fixes skipped cameras and lights, so their positions stayed in the old space and the camera moved by the null's offset. Orientation is still left as authored; a later commit re-expresses it. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
9efe75330c
commit
47051fcb82
@@ -591,7 +591,8 @@ pub(crate) struct ParentFix {
|
||||
rotation: f64,
|
||||
/// Multiply Scale.
|
||||
scale: [f64; 2],
|
||||
/// 3D AV layers retain their world-space anchor, including Z.
|
||||
/// 3D AV layers retain their world-space anchor, including Z. Cameras and lights also
|
||||
/// re-express their Point of Interest.
|
||||
position_3d: Option<effectcraft_geom::Mat4>,
|
||||
/// A uniform relative parent transform can preserve static Orientation and Scale.
|
||||
pose_3d: Option<([f64; 3], f64)>,
|
||||
@@ -626,7 +627,8 @@ impl ParentFix {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(pr) = tr.get_mut("position") {
|
||||
// Position and a camera's or light's Point of Interest are both points in the parent's space.
|
||||
let map_point = |pr: &mut effectcraft_project::Property| {
|
||||
pr.value = map_position(&pr.value);
|
||||
for key in &mut pr.keys {
|
||||
key.value = map_position(&key.value);
|
||||
@@ -646,6 +648,12 @@ impl ParentFix {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(pr) = tr.get_mut("position") {
|
||||
map_point(pr);
|
||||
}
|
||||
if let Some(pr) = tr.get_mut("poi") {
|
||||
map_point(pr);
|
||||
}
|
||||
if let Some((orientation, factor)) = self.pose_3d {
|
||||
if let Some(pr) = tr.get_mut("orientation") {
|
||||
@@ -793,7 +801,7 @@ 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) {
|
||||
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| {
|
||||
|
||||
@@ -207,6 +207,29 @@ fn control_light_with_camera_follows_and_releases() {
|
||||
assert!(s.execute("light.controlWithCamera", json!({"layers": [cam]})).is_err());
|
||||
}
|
||||
|
||||
/// Parenting keeps a camera where it is (After Effects' Parent & Link): Position and Point of
|
||||
/// Interest are re-expressed in the null's space, so the frame does not move.
|
||||
#[test]
|
||||
fn parented_camera_keeps_its_frame_under_an_offset_null() {
|
||||
let mut s = session();
|
||||
let cam = camera(&mut s);
|
||||
let (eye0, f0) = frame(&s, cam);
|
||||
let nul = s.execute("layer.newNull", json!({})).unwrap()["layer"].as_u64().unwrap();
|
||||
s.execute("prop.set", json!({"layer": nul, "path": "transform/position", "value": [100, 60, 0]})).unwrap();
|
||||
s.execute("layer.setParent", json!({"layers": [cam], "parent": nul})).unwrap();
|
||||
let (eye1, f1) = frame(&s, cam);
|
||||
assert!((eye1 - eye0).length() < 1e-6 && (f1 - f0).length() < 1e-6, "{eye0:?} {eye1:?}");
|
||||
// The null's origin is Position minus its default anchor (50, 50): (50, 10) in the comp.
|
||||
let c = layer_in(&s, cid(&s), cam);
|
||||
assert!(close3(v3(&c, "transform/position"), [270.0, 170.0, -800.0], 1e-6), "{:?}", v3(&c, "transform/position"));
|
||||
assert!(close3(v3(&c, "transform/poi"), [270.0, 170.0, 0.0], 1e-6), "{:?}", v3(&c, "transform/poi"));
|
||||
// Moving the null carries the camera and keeps its aim.
|
||||
s.execute("prop.set", json!({"layer": nul, "path": "transform/position", "value": [140, 90, 0]})).unwrap();
|
||||
let (eye2, f2) = frame(&s, cam);
|
||||
assert!((eye2 - (eye0 + vec3(40.0, 30.0, 0.0))).length() < 1e-6, "{eye2:?}");
|
||||
assert!((f2 - f0).length() < 1e-6, "{f2:?}");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- from 3D model
|
||||
|
||||
struct DiskModels;
|
||||
|
||||
Reference in New Issue
Block a user