mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Reshape: the lengths along the path are measured once per drag, and the default reach stays the path's size (#2900)
anchor_arc_distance measured every segment again for each anchor, so a reshape cost the square of the anchor count; path.reshape now measures the positions once, and the two helpers only it used are gone. The default radius stays half the subpath's bounding-box diagonal, now measured along the path: half its length reached the far corners of a square, moving art the grab is nowhere near (reshape_moves_the_grabbed_point_and_its_neighbourhood).
This commit is contained in:
@@ -100,7 +100,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Reshape",
|
||||
[],
|
||||
None,
|
||||
"{id, x, y, dx, dy, tol=3, radius?} Reshape tool: grab the path at (x, y) (adding an anchor there unless one is within tol) and drag it by (dx, dy); nearby anchors follow with a smooth falloff over radius along the path (default: half the subpath's length)",
|
||||
"{id, x, y, dx, dy, tol=3, radius?} Reshape tool: grab the path at (x, y) (adding an anchor there unless one is within tol) and drag it by (dx, dy); nearby anchors follow with a smooth falloff over radius, measured along the path (default: half the subpath's size)",
|
||||
has_doc,
|
||||
reshape
|
||||
),
|
||||
@@ -680,22 +680,30 @@ fn reshape(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Some((i, dist)) if dist <= tol => i,
|
||||
_ => sp.insert_anchor(seg, t),
|
||||
};
|
||||
let r = radius.unwrap_or_else(|| (sp.arc_length() / 2.0).max(1.0));
|
||||
let weights: Vec<f64> = sp
|
||||
.anchors
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| {
|
||||
if i == grabbed {
|
||||
1.0
|
||||
} else {
|
||||
let q = (sp.anchor_arc_distance(grabbed, i) / r).min(1.0);
|
||||
(1.0 - q * q).powi(2)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// Each anchor's distance along the path from the grabbed one, the shorter way round a
|
||||
// closed path: the lengths are measured once, as a path can have many anchors.
|
||||
let pos = sp.anchor_arc_positions();
|
||||
let total = pos.last().copied().unwrap_or(0.0);
|
||||
// The default reach is the subpath's size, as before, now measured along it.
|
||||
let r = radius.unwrap_or_else(|| {
|
||||
let mut bp = vectorcraft_geom::BezPath::new();
|
||||
sp.to_bezpath_into(&mut bp);
|
||||
let b = vectorcraft_geom::Shape::bounding_box(&bp);
|
||||
(b.width().hypot(b.height()) / 2.0).max(1.0)
|
||||
});
|
||||
let from = pos.get(grabbed).copied().unwrap_or(0.0);
|
||||
let closed = sp.closed;
|
||||
for (i, a) in sp.anchors.iter_mut().enumerate() {
|
||||
let v = delta * weights[i];
|
||||
let w = if i == grabbed {
|
||||
1.0
|
||||
} else {
|
||||
let d = (pos.get(i).copied().unwrap_or(f64::INFINITY) - from).abs();
|
||||
let d = if closed { d.min(total - d) } else { d };
|
||||
// A distance or radius that isn't finite gives NaN here, which `min` reads as 1: no move.
|
||||
let q = (d / r).min(1.0);
|
||||
(1.0 - q * q).powi(2)
|
||||
};
|
||||
let v = delta * w;
|
||||
a.p += v;
|
||||
a.h_in += v;
|
||||
a.h_out += v;
|
||||
|
||||
@@ -2355,8 +2355,8 @@ fn reshape_falloff_follows_arc_length_on_open_paths() {
|
||||
}),
|
||||
)
|
||||
.unwrap()["id"]
|
||||
.as_u64()
|
||||
.unwrap(),
|
||||
.as_u64()
|
||||
.unwrap(),
|
||||
);
|
||||
s.execute("path.reshape", &json!({"id": id.0, "x": 300.0, "y": 0.0, "dx": 0.0, "dy": 100.0})).unwrap();
|
||||
let pts: Vec<_> = s.doc().unwrap().doc.node(id).unwrap().path_data().unwrap().subpaths[0].anchors.iter().map(|a| a.p).collect();
|
||||
|
||||
+2
-21
@@ -229,30 +229,11 @@ impl SubPath {
|
||||
}
|
||||
let mut pos = vec![0.0];
|
||||
for i in 0..self.segment_count() {
|
||||
let len = kurbo::ParamCurveArclen::arclen(&self.segment(i), 1e-6);
|
||||
pos.push(pos[i] + len);
|
||||
let len = kurbo::ParamCurveArclen::arclen(&self.segment(i), 1e-3);
|
||||
pos.push(pos.last().copied().unwrap_or(0.0) + len);
|
||||
}
|
||||
pos
|
||||
}
|
||||
/// Total length of all segments.
|
||||
pub fn arc_length(&self) -> f64 {
|
||||
self.anchor_arc_positions().last().copied().unwrap_or(0.0)
|
||||
}
|
||||
/// Shortest distance along this subpath between two anchors.
|
||||
pub fn anchor_arc_distance(&self, a: usize, b: usize) -> f64 {
|
||||
let pos = self.anchor_arc_positions();
|
||||
let n = self.anchors.len();
|
||||
if a >= n || b >= n {
|
||||
return f64::INFINITY;
|
||||
}
|
||||
let d = (pos[a] - pos[b]).abs();
|
||||
if self.closed {
|
||||
let total = pos.last().copied().unwrap_or(0.0);
|
||||
if total > 0.0 { d.min(total - d) } else { d }
|
||||
} else {
|
||||
d
|
||||
}
|
||||
}
|
||||
pub fn to_bezpath_into(&self, out: &mut BezPath) {
|
||||
let Some(first) = self.anchors.first() else { return };
|
||||
out.move_to(first.p);
|
||||
|
||||
Reference in New Issue
Block a user