refactor for simpler circle command and removed duplicated code

This commit is contained in:
Franz Dietrich 2025-10-09 13:23:52 +02:00
parent 25753b47ce
commit 7da0dcf141
5 changed files with 59 additions and 191 deletions

View File

@ -61,10 +61,11 @@ pub trait CurvedMovement: WithCommands {
{ {
let r: Precision = radius.into(); let r: Precision = radius.into();
let a: Precision = angle.into(); let a: Precision = angle.into();
self.get_commands_mut().push(TurtleCommand::CircleLeft { self.get_commands_mut().push(TurtleCommand::Circle {
radius: r, radius: r,
angle: a, angle: a,
steps, steps,
direction: crate::circle_geometry::CircleDirection::Left,
}); });
self self
} }
@ -76,10 +77,11 @@ pub trait CurvedMovement: WithCommands {
{ {
let r: Precision = radius.into(); let r: Precision = radius.into();
let a: Precision = angle.into(); let a: Precision = angle.into();
self.get_commands_mut().push(TurtleCommand::CircleRight { self.get_commands_mut().push(TurtleCommand::Circle {
radius: r, radius: r,
angle: a, angle: a,
steps, steps,
direction: crate::circle_geometry::CircleDirection::Right,
}); });
self self
} }

View File

@ -15,15 +15,11 @@ pub enum TurtleCommand {
Right(Precision), // degrees Right(Precision), // degrees
// Circle drawing // Circle drawing
CircleLeft { Circle {
radius: Precision,
angle: Precision, // degrees
steps: usize,
},
CircleRight {
radius: Precision, radius: Precision,
angle: Precision, // degrees angle: Precision, // degrees
steps: usize, steps: usize,
direction: crate::circle_geometry::CircleDirection,
}, },
// Pen control // Pen control

View File

@ -144,21 +144,14 @@ pub(crate) fn render_world_with_tween(
if let Some(tween) = active_tween { if let Some(tween) = active_tween {
if tween.start_state.pen_down { if tween.start_state.pen_down {
match &tween.command { match &tween.command {
crate::commands::TurtleCommand::CircleLeft { crate::commands::TurtleCommand::Circle {
radius, radius,
angle, angle,
steps, steps,
direction,
} => { } => {
// Draw arc segments from start to current position // Draw arc segments from start to current position
draw_tween_arc_left(tween, *radius, *angle, *steps); draw_tween_arc(tween, *radius, *angle, *steps, *direction);
}
crate::commands::TurtleCommand::CircleRight {
radius,
angle,
steps,
} => {
// Draw arc segments from start to current position
draw_tween_arc_right(tween, *radius, *angle, *steps);
} }
_ if should_draw_tween_line(&tween.command) => { _ if should_draw_tween_line(&tween.command) => {
// Draw straight line for other movement commands // Draw straight line for other movement commands
@ -200,59 +193,19 @@ fn should_draw_tween_line(command: &crate::commands::TurtleCommand) -> bool {
) )
} }
/// Draw arc segments for circle_left tween animation /// Draw arc segments for circle tween animation
fn draw_tween_arc_left( fn draw_tween_arc(
tween: &crate::tweening::CommandTween, tween: &crate::tweening::CommandTween,
radius: f32, radius: f32,
total_angle: f32, total_angle: f32,
steps: usize, steps: usize,
direction: CircleDirection,
) { ) {
let geom = CircleGeometry::new( let geom = CircleGeometry::new(
tween.start_state.position, tween.start_state.position,
tween.start_state.heading, tween.start_state.heading,
radius, radius,
CircleDirection::Left, direction,
);
// Debug: draw center
draw_circle(geom.center.x, geom.center.y, 5.0, GRAY);
// Calculate how much of the arc we've traveled based on tween progress
// Use the same eased progress as the turtle position for synchronized animation
let elapsed = (get_time() - tween.start_time) as f32;
let t = (elapsed / tween.duration as f32).min(1.0);
let progress = CubicInOut.tween(1.0, t); // tween from 0 to 1
let angle_traveled = total_angle.to_radians() * progress;
let (rotation_degrees, arc_degrees) = geom.draw_arc_params_partial(angle_traveled);
// Adjust radius inward by half the line width so the line sits on the turtle's path
let draw_radius = radius - tween.start_state.pen_width / 2.0;
// Draw the partial arc
draw_arc(
geom.center.x,
geom.center.y,
steps as u8,
draw_radius,
rotation_degrees,
tween.start_state.pen_width,
arc_degrees,
tween.start_state.color,
);
}
/// Draw arc segments for circle_right tween animation
fn draw_tween_arc_right(
tween: &crate::tweening::CommandTween,
radius: f32,
total_angle: f32,
steps: usize,
) {
let geom = CircleGeometry::new(
tween.start_state.position,
tween.start_state.heading,
radius,
CircleDirection::Right,
); );
// Debug: draw center // Debug: draw center

View File

@ -62,18 +62,14 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
state.heading += degrees.to_radians(); state.heading += degrees.to_radians();
} }
TurtleCommand::CircleLeft { TurtleCommand::Circle {
radius, radius,
angle, angle,
steps, steps,
direction,
} => { } => {
let start_heading = state.heading; let start_heading = state.heading;
let geom = CircleGeometry::new( let geom = CircleGeometry::new(state.position, start_heading, *radius, *direction);
state.position,
start_heading,
*radius,
CircleDirection::Left,
);
if state.pen_down { if state.pen_down {
let (rotation_degrees, arc_degrees) = geom.draw_arc_params(*angle); let (rotation_degrees, arc_degrees) = geom.draw_arc_params(*angle);
@ -91,39 +87,10 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
// Update turtle position and heading // Update turtle position and heading
state.position = geom.position_at_angle(angle.to_radians()); state.position = geom.position_at_angle(angle.to_radians());
state.heading = start_heading - angle.to_radians(); state.heading = match direction {
} CircleDirection::Left => start_heading - angle.to_radians(),
CircleDirection::Right => start_heading + angle.to_radians(),
TurtleCommand::CircleRight { };
radius,
angle,
steps,
} => {
let start_heading = state.heading;
let geom = CircleGeometry::new(
state.position,
start_heading,
*radius,
CircleDirection::Right,
);
if state.pen_down {
let (rotation_degrees, arc_degrees) = geom.draw_arc_params(*angle);
world.add_command(DrawCommand::Arc {
center: geom.center,
radius: *radius - state.pen_width, // Adjust radius for pen width to keep arc inside
rotation: rotation_degrees,
arc: arc_degrees,
color: state.color,
width: state.pen_width,
sides: *steps as u8,
});
}
// Update turtle position and heading
state.position = geom.position_at_angle(angle.to_radians());
state.heading = start_heading + angle.to_radians();
} }
TurtleCommand::PenUp => { TurtleCommand::PenUp => {
@ -225,56 +192,18 @@ pub fn add_draw_for_completed_tween(
}); });
} }
} }
TurtleCommand::CircleLeft { TurtleCommand::Circle {
radius, radius,
angle, angle,
steps, steps,
direction,
} => { } => {
if start_state.pen_down { if start_state.pen_down {
let geom = CircleGeometry::new( let geom = CircleGeometry::new(
start_state.position, start_state.position,
start_state.heading, start_state.heading,
*radius, *radius,
CircleDirection::Left, *direction,
);
let (rotation_degrees, arc_degrees) = geom.draw_arc_params(*angle);
world.add_command(DrawCommand::Arc {
center: geom.center,
radius: *radius - start_state.pen_width / 2.0,
rotation: rotation_degrees,
arc: arc_degrees,
color: start_state.color,
width: start_state.pen_width,
sides: *steps as u8,
});
// Add endpoint circles for smooth joins
world.add_command(DrawCommand::Circle {
center: start_state.position,
radius: start_state.pen_width / 2.0,
color: start_state.color,
filled: true,
});
world.add_command(DrawCommand::Circle {
center: end_state.position,
radius: start_state.pen_width / 2.0,
color: start_state.color,
filled: true,
});
}
}
TurtleCommand::CircleRight {
radius,
angle,
steps,
} => {
if start_state.pen_down {
let geom = CircleGeometry::new(
start_state.position,
start_state.heading,
*radius,
CircleDirection::Right,
); );
let (rotation_degrees, arc_degrees) = geom.draw_arc_params(*angle); let (rotation_degrees, arc_degrees) = geom.draw_arc_params(*angle);

View File

@ -84,22 +84,19 @@ impl TweenController {
let progress = tween.heading_tweener.move_to(elapsed); let progress = tween.heading_tweener.move_to(elapsed);
state.position = match &tween.command { state.position = match &tween.command {
TurtleCommand::CircleLeft { radius, angle, .. } => { TurtleCommand::Circle {
radius,
angle,
direction,
..
} => {
let angle_traveled = angle.to_radians() * progress; let angle_traveled = angle.to_radians() * progress;
calculate_circle_left_position( calculate_circle_position(
tween.start_state.position,
tween.start_state.heading,
*radius,
angle_traveled,
)
}
TurtleCommand::CircleRight { radius, angle, .. } => {
let angle_traveled = angle.to_radians() * progress;
calculate_circle_right_position(
tween.start_state.position, tween.start_state.position,
tween.start_state.heading, tween.start_state.heading,
*radius, *radius,
angle_traveled, angle_traveled,
*direction,
) )
} }
_ => { _ => {
@ -110,12 +107,16 @@ impl TweenController {
// Heading changes proportionally with progress for all commands // Heading changes proportionally with progress for all commands
state.heading = match &tween.command { state.heading = match &tween.command {
TurtleCommand::CircleLeft { angle, .. } => { TurtleCommand::Circle {
tween.start_state.heading - angle.to_radians() * progress angle, direction, ..
} } => match direction {
TurtleCommand::CircleRight { angle, .. } => { CircleDirection::Left => {
tween.start_state.heading + angle.to_radians() * progress tween.start_state.heading - angle.to_radians() * progress
} }
CircleDirection::Right => {
tween.start_state.heading + angle.to_radians() * progress
}
},
TurtleCommand::Left(angle) => { TurtleCommand::Left(angle) => {
tween.start_state.heading - angle.to_radians() * progress tween.start_state.heading - angle.to_radians() * progress
} }
@ -219,8 +220,7 @@ impl TweenController {
command, command,
TurtleCommand::Forward(_) TurtleCommand::Forward(_)
| TurtleCommand::Backward(_) | TurtleCommand::Backward(_)
| TurtleCommand::CircleLeft { .. } | TurtleCommand::Circle { .. }
| TurtleCommand::CircleRight { .. }
| TurtleCommand::Goto(_) | TurtleCommand::Goto(_)
) )
} }
@ -234,8 +234,7 @@ impl TweenController {
// Rotation speed: assume 180 degrees per second at speed 100 // Rotation speed: assume 180 degrees per second at speed 100
angle.abs() / (speed * 1.8) angle.abs() / (speed * 1.8)
} }
TurtleCommand::CircleLeft { radius, angle, .. } TurtleCommand::Circle { radius, angle, .. } => {
| TurtleCommand::CircleRight { radius, angle, .. } => {
let arc_length = radius * angle.to_radians().abs(); let arc_length = radius * angle.to_radians().abs();
arc_length / speed arc_length / speed
} }
@ -272,25 +271,24 @@ impl TweenController {
TurtleCommand::Right(angle) => { TurtleCommand::Right(angle) => {
target.heading += angle.to_radians(); target.heading += angle.to_radians();
} }
TurtleCommand::CircleLeft { radius, angle, .. } => { TurtleCommand::Circle {
radius,
angle,
direction,
..
} => {
// Use helper function to calculate final position // Use helper function to calculate final position
target.position = calculate_circle_left_position( target.position = calculate_circle_position(
current.position, current.position,
current.heading, current.heading,
*radius, *radius,
angle.to_radians(), angle.to_radians(),
*direction,
); );
target.heading = current.heading - angle.to_radians(); target.heading = match direction {
} CircleDirection::Left => current.heading - angle.to_radians(),
TurtleCommand::CircleRight { radius, angle, .. } => { CircleDirection::Right => current.heading + angle.to_radians(),
// Use helper function to calculate final position };
target.position = calculate_circle_right_position(
current.position,
current.heading,
*radius,
angle.to_radians(),
);
target.heading = current.heading + angle.to_radians();
} }
TurtleCommand::Goto(coord) => { TurtleCommand::Goto(coord) => {
target.position = *coord; target.position = *coord;
@ -331,24 +329,14 @@ impl TweenController {
} }
} }
/// Calculate position on a circular arc for circle_left /// Calculate position on a circular arc
fn calculate_circle_left_position( fn calculate_circle_position(
start_pos: Vec2, start_pos: Vec2,
start_heading: f32, start_heading: f32,
radius: f32, radius: f32,
angle_traveled: f32, // How much of the total angle we've traveled (in radians) angle_traveled: f32, // How much of the total angle we've traveled (in radians)
direction: CircleDirection,
) -> Vec2 { ) -> Vec2 {
let geom = CircleGeometry::new(start_pos, start_heading, radius, CircleDirection::Left); let geom = CircleGeometry::new(start_pos, start_heading, radius, direction);
geom.position_at_angle(angle_traveled)
}
/// Calculate position on a circular arc for circle_right
fn calculate_circle_right_position(
start_pos: Vec2,
start_heading: f32,
radius: f32,
angle_traveled: f32, // How much of the total angle we've traveled (in radians)
) -> Vec2 {
let geom = CircleGeometry::new(start_pos, start_heading, radius, CircleDirection::Right);
geom.position_at_angle(angle_traveled) geom.position_at_angle(angle_traveled)
} }