feat(grips): improve direct editing

Refit arcs from fixed control points and highlight hovered grips.
Let Lengthen preview from the cursor or accept a typed distance.
This commit is contained in:
Hakan Seven 2026-08-04 16:27:03 +03:00
commit 539d83de47
10 changed files with 421 additions and 59 deletions

View file

@ -24,10 +24,18 @@ pub struct GripEdit {
pub origin_world: DVec3,
/// Last world-space cursor position (needed for incremental delta on translate drags).
pub last_world: DVec3,
/// How cursor movement modifies the selected grip.
pub mode: GripEditMode,
/// Every hot grip moved by this edit. A normal grip edit contains one target.
pub targets: Vec<GripTarget>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GripEditMode {
Stretch,
Lengthen,
}
#[derive(Clone, Debug)]
pub struct GripTarget {
pub handle: Handle,
@ -48,6 +56,7 @@ impl GripEdit {
grip_id,
origin_world: world,
last_world: world,
mode: GripEditMode::Stretch,
targets: vec![GripTarget {
handle,
grip_id,
@ -56,6 +65,12 @@ impl GripEdit {
}],
}
}
pub fn lengthen(handle: Handle, grip_id: usize, world: DVec3) -> Self {
let mut edit = Self::single(handle, grip_id, false, world);
edit.mode = GripEditMode::Lengthen;
edit
}
}
// ── Screen-space helpers ───────────────────────────────────────────────────

View file

@ -245,6 +245,57 @@ pub fn apply_grip(entity: &mut EntityType, grip_id: usize, apply: crate::scene::
EntityTypeOps::apply_grip(entity, grip_id, apply);
}
/// Rebuild an arc from its drag-start control points and the point grips moved
/// in the current frame. Returns `None` for non-arc entities and `Some(false)`
/// when the requested points are temporarily degenerate.
pub fn refit_arc_grips(
entity: &mut EntityType,
original: &EntityType,
edits: &[(usize, glam::DVec3)],
) -> Option<bool> {
let EntityType::Arc(arc) = entity else {
return None;
};
let EntityType::Arc(original_arc) = original else {
return None;
};
let edits = match planar_ocs_normal(original) {
Some(normal) => edits
.iter()
.map(|&(grip_id, point)| {
let (x, y, z) =
super::transform::wcs_point_to_ocs((point.x, point.y, point.z), normal);
(grip_id, glam::DVec3::new(x, y, z))
})
.collect::<Vec<_>>(),
None => edits.to_vec(),
};
Some(crate::entities::arc::refit_grips(
arc,
original_arc,
&edits,
))
}
/// Convert a world-space cursor point into the value expected by an
/// interactive grip-menu action.
pub fn grip_menu_point_value(
entity: &EntityType,
grip_id: usize,
action: crate::scene::model::object::GripMenuAction,
point: glam::DVec3,
) -> Option<f64> {
let point = match planar_ocs_normal(entity) {
Some(normal) => {
let (x, y, z) =
super::transform::wcs_point_to_ocs((point.x, point.y, point.z), normal);
glam::DVec3::new(x, y, z)
}
None => point,
};
EntityTypeOps::grip_menu_point_value(entity, grip_id, action, point)
}
pub fn apply_transform(entity: &mut EntityType, t: &EntityTransform) {
EntityTypeOps::apply_transform(entity, t);
}