mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 21:57:12 -06:00
Perspective: a homography product that overflows is refused instead of stored or drawn
Homography::then_after returned products whose entries could overflow to infinity or become NaN. A move, transform or grid reproject of type or a symbol instance whose stored projection has large finite entries stored such a product, and the type was then drawn flat, since a projection that is not finite reads as none. - Homography::checked_then_after returns None when an entry of the product is not finite. - project_node composes each type and symbol instance projection with it and returns false, leaving the object as it is, when one would not be finite; moves and transforms in perspective and Define Perspective Grid with reproject then return the horizon error and leave the document unchanged. - transform_map, attach_homography and size_homography return None for such a product. - Edit Text returns the horizon error, and does not enter isolation mode, when the type cannot be shown flat where it is drawn.
This commit is contained in:
@@ -113,6 +113,39 @@ fn moving_type_in_perspective_moves_its_projection() {
|
||||
let _ = rect;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_type_whose_projection_would_overflow_is_refused() {
|
||||
let (mut s, _) = persp_session();
|
||||
let id = persp_text(&mut s);
|
||||
let NodeKind::Text(t) = &node(&s, id).kind else { panic!("type") };
|
||||
let flat = t.bounds().unwrap();
|
||||
// A stored projection that draws the type flat where it is, with every entry scaled by `k`: a
|
||||
// document can hold it (its entries are finite) and the type draws, but its product with a
|
||||
// scale by 2 in perspective overflows.
|
||||
let step = grid(&s).transform_map(Plane::Right, 0.0, vectorcraft_geom::Affine::scale(2.0), 0.0).unwrap();
|
||||
let scaled = |e: i32| {
|
||||
let k = 2f64.powf(f64::from(e) / 16.0);
|
||||
vectorcraft_geom::Homography([[k, 0.0, 0.0], [0.0, k, 0.0], [0.0, 0.0, k]])
|
||||
};
|
||||
let finite = |m: &vectorcraft_geom::Homography| m.to_array().iter().all(|v| v.is_finite());
|
||||
let draws = |m: &vectorcraft_geom::Homography| {
|
||||
let drawn = m.map_rect_bbox(flat);
|
||||
drawn.is_some_and(|b| step.map_rect_bbox(b).is_some()) && vectorcraft_geom::Homography::from_array(m.to_array()) == Some(*m)
|
||||
};
|
||||
let e = (0..16 * 1024).find(|e| draws(&scaled(*e)) && !finite(&step.then_after(&scaled(*e)))).unwrap_or_else(|| panic!("{step:?} {flat:?}"));
|
||||
let big = scaled(e);
|
||||
let mut doc = s.doc().unwrap().doc.clone();
|
||||
let mut extras = std::sync::Arc::make_mut(&mut doc).node_mut(id).unwrap().extras_mut();
|
||||
extras.perspective.as_deref_mut().unwrap().projection = Some(big.to_array());
|
||||
drop(extras);
|
||||
s.doc_mut().unwrap().doc = doc;
|
||||
let before = (node(&s, id), undo_len(&s));
|
||||
assert!(s.execute("perspective.transform", &json!({"ids": [id.0], "matrix": [2, 0, 0, 2, 0, 0]})).is_err());
|
||||
assert_eq!((node(&s, id), undo_len(&s)), before, "the document is unchanged");
|
||||
let stored = node(&s, id).extras().perspective.as_deref().and_then(|p| p.projection).unwrap();
|
||||
assert!(stored.iter().all(|v| v.is_finite()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_text_shows_it_flat_in_isolation_then_projects_it_again() {
|
||||
let (mut s, _) = persp_session();
|
||||
@@ -141,6 +174,37 @@ fn edit_text_shows_it_flat_in_isolation_then_projects_it_again() {
|
||||
assert!(s2.execute("perspective.editText", &json!({"id": rect.0})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_text_whose_flat_view_would_overflow_is_refused() {
|
||||
let (mut s, _) = persp_session();
|
||||
let id = persp_text(&mut s);
|
||||
let NodeKind::Text(t) = &node(&s, id).kind else { panic!("type") };
|
||||
let flat = t.bounds().unwrap();
|
||||
// A stored projection that draws the type 4 times its size, with every entry scaled by `k`: a
|
||||
// document can hold it and the type draws, but the projection that shows it flat where it is
|
||||
// drawn overflows.
|
||||
let scaled = |e: i32| {
|
||||
let k = 2f64.powf(f64::from(e) / 16.0);
|
||||
vectorcraft_geom::Homography([[4.0 * k, 0.0, 0.0], [0.0, 4.0 * k, 0.0], [0.0, 0.0, k]])
|
||||
};
|
||||
let overflows = |m: &vectorcraft_geom::Homography| {
|
||||
let Some(drawn) = m.map_rect_bbox(flat) else { return false };
|
||||
let shift = vectorcraft_geom::Affine::translate(drawn.center() - flat.center());
|
||||
let moved = m.then_after(&vectorcraft_geom::Homography::from_affine(shift.inverse()));
|
||||
vectorcraft_geom::Homography::from_array(m.to_array()) == Some(*m) && moved.to_array().iter().any(|v| !v.is_finite())
|
||||
};
|
||||
let e = (0..16 * 1024).find(|e| overflows(&scaled(*e))).unwrap_or_else(|| panic!("{flat:?}"));
|
||||
let mut doc = s.doc().unwrap().doc.clone();
|
||||
let mut extras = std::sync::Arc::make_mut(&mut doc).node_mut(id).unwrap().extras_mut();
|
||||
extras.perspective.as_deref_mut().unwrap().projection = Some(scaled(e).to_array());
|
||||
drop(extras);
|
||||
s.doc_mut().unwrap().doc = doc;
|
||||
let before = (node(&s, id), undo_len(&s));
|
||||
assert!(s.execute("perspective.editText", &json!({"id": id.0})).is_err());
|
||||
assert_eq!((node(&s, id), undo_len(&s)), before, "the document is unchanged");
|
||||
assert_eq!(s.doc().unwrap().isolation, None, "not in isolation mode");
|
||||
}
|
||||
|
||||
/// Edit Text, which shows the type in isolation mode, is off while an opacity mask is edited.
|
||||
#[test]
|
||||
fn edit_text_is_off_while_an_opacity_mask_is_edited() {
|
||||
|
||||
Reference in New Issue
Block a user