unify draws and state updates from instant and animated drawing
This commit is contained in:
parent
c96d66247e
commit
033a1982fc
@ -9,8 +9,135 @@ use macroquad::prelude::*;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::general::AnimationSpeed;
|
use crate::general::AnimationSpeed;
|
||||||
|
|
||||||
|
/// Execute side effects for commands that don't involve movement
|
||||||
|
/// Returns true if the command was handled (caller should skip movement processing)
|
||||||
|
pub fn execute_command_side_effects(
|
||||||
|
command: &TurtleCommand,
|
||||||
|
state: &mut TurtleState,
|
||||||
|
commands: &mut Vec<DrawCommand>,
|
||||||
|
) -> bool {
|
||||||
|
match command {
|
||||||
|
TurtleCommand::BeginFill => {
|
||||||
|
if state.filling.is_some() {
|
||||||
|
tracing::warn!("begin_fill() called while already filling");
|
||||||
|
}
|
||||||
|
let fill_color = state.fill_color.unwrap_or_else(|| {
|
||||||
|
tracing::warn!("No fill_color set, using black");
|
||||||
|
BLACK
|
||||||
|
});
|
||||||
|
state.begin_fill(fill_color);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
TurtleCommand::EndFill => {
|
||||||
|
if let Some(mut fill_state) = state.filling.take() {
|
||||||
|
if !fill_state.current_contour.is_empty() {
|
||||||
|
fill_state.contours.push(fill_state.current_contour);
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = tracing::debug_span!("end_fill", contours = fill_state.contours.len());
|
||||||
|
let _enter = span.enter();
|
||||||
|
|
||||||
|
for (i, contour) in fill_state.contours.iter().enumerate() {
|
||||||
|
tracing::debug!(contour_idx = i, vertices = contour.len(), "Contour info");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fill_state.contours.is_empty() {
|
||||||
|
if let Ok(mesh_data) = tessellation::tessellate_multi_contour(
|
||||||
|
&fill_state.contours,
|
||||||
|
fill_state.fill_color,
|
||||||
|
) {
|
||||||
|
tracing::debug!(
|
||||||
|
contours = fill_state.contours.len(),
|
||||||
|
"Successfully tessellated contours"
|
||||||
|
);
|
||||||
|
commands.push(DrawCommand::Mesh(mesh_data));
|
||||||
|
} else {
|
||||||
|
tracing::error!("Failed to tessellate contours");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::warn!("end_fill() called without begin_fill()");
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
TurtleCommand::PenUp => {
|
||||||
|
state.pen_down = false;
|
||||||
|
if state.filling.is_some() {
|
||||||
|
tracing::debug!("PenUp: Closing current contour");
|
||||||
|
}
|
||||||
|
state.close_fill_contour();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
TurtleCommand::PenDown => {
|
||||||
|
state.pen_down = true;
|
||||||
|
if state.filling.is_some() {
|
||||||
|
tracing::debug!(
|
||||||
|
x = state.position.x,
|
||||||
|
y = state.position.y,
|
||||||
|
"PenDown: Starting new contour"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
state.start_fill_contour();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => false, // Not a side-effect-only command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record fill vertices after movement commands have updated state
|
||||||
|
pub fn record_fill_vertices_after_movement(
|
||||||
|
command: &TurtleCommand,
|
||||||
|
start_state: &TurtleState,
|
||||||
|
state: &mut TurtleState,
|
||||||
|
) {
|
||||||
|
if state.filling.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match command {
|
||||||
|
TurtleCommand::Circle {
|
||||||
|
radius,
|
||||||
|
angle,
|
||||||
|
steps,
|
||||||
|
direction,
|
||||||
|
} => {
|
||||||
|
let geom = CircleGeometry::new(
|
||||||
|
start_state.position,
|
||||||
|
start_state.heading,
|
||||||
|
*radius,
|
||||||
|
*direction,
|
||||||
|
);
|
||||||
|
state.record_fill_vertices_for_arc(
|
||||||
|
geom.center,
|
||||||
|
*radius,
|
||||||
|
geom.start_angle_from_center,
|
||||||
|
angle.to_radians(),
|
||||||
|
*direction,
|
||||||
|
*steps as u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TurtleCommand::Move(_) | TurtleCommand::Goto(_) => {
|
||||||
|
state.record_fill_vertex();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a single turtle command, updating state and adding draw commands
|
/// Execute a single turtle command, updating state and adding draw commands
|
||||||
pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world: &mut TurtleWorld) {
|
pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world: &mut TurtleWorld) {
|
||||||
|
// Try to execute as side-effect-only command first
|
||||||
|
if execute_command_side_effects(command, state, &mut world.commands) {
|
||||||
|
return; // Command fully handled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store start state for fill vertex recording
|
||||||
|
let start_state = state.clone();
|
||||||
|
|
||||||
|
// Execute movement and appearance commands
|
||||||
match command {
|
match command {
|
||||||
TurtleCommand::Move(distance) => {
|
TurtleCommand::Move(distance) => {
|
||||||
let start = state.position;
|
let start = state.position;
|
||||||
@ -18,9 +145,6 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
|
|||||||
let dy = distance * state.heading.sin();
|
let dy = distance * state.heading.sin();
|
||||||
state.position = vec2(state.position.x + dx, state.position.y + dy);
|
state.position = vec2(state.position.x + dx, state.position.y + dy);
|
||||||
|
|
||||||
// Record vertex for fill if filling
|
|
||||||
state.record_fill_vertex();
|
|
||||||
|
|
||||||
if state.pen_down {
|
if state.pen_down {
|
||||||
// Draw line segment with round caps (caps handled by tessellate_stroke)
|
// Draw line segment with round caps (caps handled by tessellate_stroke)
|
||||||
if let Ok(mesh_data) = tessellation::tessellate_stroke(
|
if let Ok(mesh_data) = tessellation::tessellate_stroke(
|
||||||
@ -70,67 +194,12 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
|
|||||||
CircleDirection::Left => start_heading - angle.to_radians(),
|
CircleDirection::Left => start_heading - angle.to_radians(),
|
||||||
CircleDirection::Right => start_heading + angle.to_radians(),
|
CircleDirection::Right => start_heading + angle.to_radians(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Record vertices along arc for fill if filling
|
|
||||||
state.record_fill_vertices_for_arc(
|
|
||||||
geom.center,
|
|
||||||
*radius,
|
|
||||||
geom.start_angle_from_center,
|
|
||||||
angle.to_radians(),
|
|
||||||
*direction,
|
|
||||||
*steps as u32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::PenUp => {
|
|
||||||
state.pen_down = false;
|
|
||||||
// Close current contour if filling
|
|
||||||
if state.filling.is_some() {
|
|
||||||
tracing::debug!("PenUp: Closing current contour");
|
|
||||||
}
|
|
||||||
state.close_fill_contour();
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::PenDown => {
|
|
||||||
state.pen_down = true;
|
|
||||||
// Start new contour if filling
|
|
||||||
if state.filling.is_some() {
|
|
||||||
tracing::debug!(
|
|
||||||
x = state.position.x,
|
|
||||||
y = state.position.y,
|
|
||||||
"PenDown: Starting new contour"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
state.start_fill_contour();
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::SetColor(color) => {
|
|
||||||
state.color = *color;
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::SetFillColor(color) => {
|
|
||||||
state.fill_color = *color;
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::SetPenWidth(width) => {
|
|
||||||
state.pen_width = *width;
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::SetSpeed(speed) => {
|
|
||||||
state.set_speed(*speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::SetShape(shape) => {
|
|
||||||
state.shape = shape.clone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TurtleCommand::Goto(coord) => {
|
TurtleCommand::Goto(coord) => {
|
||||||
let start = state.position;
|
let start = state.position;
|
||||||
state.position = *coord;
|
state.position = *coord;
|
||||||
|
|
||||||
// Record vertex for fill if filling
|
|
||||||
state.record_fill_vertex();
|
|
||||||
|
|
||||||
if state.pen_down {
|
if state.pen_down {
|
||||||
// Draw line segment with round caps
|
// Draw line segment with round caps
|
||||||
if let Ok(mesh_data) = tessellation::tessellate_stroke(
|
if let Ok(mesh_data) = tessellation::tessellate_stroke(
|
||||||
@ -144,66 +213,21 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TurtleCommand::SetHeading(heading) => {
|
// Appearance commands
|
||||||
state.heading = *heading;
|
TurtleCommand::SetColor(color) => state.color = *color,
|
||||||
|
TurtleCommand::SetFillColor(color) => state.fill_color = *color,
|
||||||
|
TurtleCommand::SetPenWidth(width) => state.pen_width = *width,
|
||||||
|
TurtleCommand::SetSpeed(speed) => state.set_speed(*speed),
|
||||||
|
TurtleCommand::SetShape(shape) => state.shape = shape.clone(),
|
||||||
|
TurtleCommand::SetHeading(heading) => state.heading = *heading,
|
||||||
|
TurtleCommand::ShowTurtle => state.visible = true,
|
||||||
|
TurtleCommand::HideTurtle => state.visible = false,
|
||||||
|
|
||||||
|
_ => {} // Already handled by execute_command_side_effects
|
||||||
}
|
}
|
||||||
|
|
||||||
TurtleCommand::ShowTurtle => {
|
// Record fill vertices AFTER movement
|
||||||
state.visible = true;
|
record_fill_vertices_after_movement(command, &start_state, state);
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::HideTurtle => {
|
|
||||||
state.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::BeginFill => {
|
|
||||||
if state.filling.is_some() {
|
|
||||||
tracing::warn!("begin_fill() called while already filling");
|
|
||||||
}
|
|
||||||
|
|
||||||
let fill_color = state.fill_color.unwrap_or_else(|| {
|
|
||||||
tracing::warn!("No fill_color set, using black");
|
|
||||||
BLACK
|
|
||||||
});
|
|
||||||
|
|
||||||
state.begin_fill(fill_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
TurtleCommand::EndFill => {
|
|
||||||
if let Some(mut fill_state) = state.filling.take() {
|
|
||||||
// Close final contour if it has vertices
|
|
||||||
if !fill_state.current_contour.is_empty() {
|
|
||||||
fill_state.contours.push(fill_state.current_contour);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug output
|
|
||||||
let span = tracing::debug_span!("end_fill", contours = fill_state.contours.len());
|
|
||||||
let _enter = span.enter();
|
|
||||||
|
|
||||||
for (i, contour) in fill_state.contours.iter().enumerate() {
|
|
||||||
tracing::debug!(contour_idx = i, vertices = contour.len(), "Contour info");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create fill command - Lyon will handle EvenOdd automatically with multiple contours
|
|
||||||
if !fill_state.contours.is_empty() {
|
|
||||||
if let Ok(mesh_data) = tessellation::tessellate_multi_contour(
|
|
||||||
&fill_state.contours,
|
|
||||||
fill_state.fill_color,
|
|
||||||
) {
|
|
||||||
tracing::debug!(
|
|
||||||
contours = fill_state.contours.len(),
|
|
||||||
"Successfully tessellated contours"
|
|
||||||
);
|
|
||||||
world.add_command(DrawCommand::Mesh(mesh_data));
|
|
||||||
} else {
|
|
||||||
tracing::error!("Failed to tessellate contours");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::warn!("end_fill() called without begin_fill()");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add drawing command for a completed tween (state transition already occurred)
|
/// Add drawing command for a completed tween (state transition already occurred)
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
use crate::circle_geometry::{CircleDirection, CircleGeometry};
|
use crate::circle_geometry::{CircleDirection, CircleGeometry};
|
||||||
use crate::commands::{CommandQueue, TurtleCommand};
|
use crate::commands::{CommandQueue, TurtleCommand};
|
||||||
use crate::general::AnimationSpeed;
|
use crate::general::AnimationSpeed;
|
||||||
use crate::state::{DrawCommand, TurtleState};
|
use crate::state::TurtleState;
|
||||||
use crate::tessellation;
|
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
use tween::{CubicInOut, TweenValue, Tweener};
|
use tween::{CubicInOut, TweenValue, Tweener};
|
||||||
|
|
||||||
@ -94,107 +93,41 @@ impl TweenController {
|
|||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Capture start state BEFORE executing this command
|
|
||||||
let start_state = state.clone();
|
let start_state = state.clone();
|
||||||
|
|
||||||
// Handle SetSpeed command to potentially switch modes
|
// Handle SetSpeed command to potentially switch modes
|
||||||
if let TurtleCommand::SetSpeed(new_speed) = &command {
|
if let TurtleCommand::SetSpeed(new_speed) = &command {
|
||||||
state.set_speed(*new_speed);
|
state.set_speed(*new_speed);
|
||||||
self.speed = *new_speed;
|
self.speed = *new_speed;
|
||||||
// If speed switched to animated mode, exit instant mode processing
|
|
||||||
if matches!(self.speed, AnimationSpeed::Animated(_)) {
|
if matches!(self.speed, AnimationSpeed::Animated(_)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For commands with side effects (fill operations), handle specially
|
// Execute side-effect-only commands using centralized helper
|
||||||
match &command {
|
if crate::execution::execute_command_side_effects(&command, state, commands) {
|
||||||
TurtleCommand::BeginFill => {
|
continue; // Command fully handled
|
||||||
let fill_color = state.fill_color.unwrap_or(macroquad::prelude::BLACK);
|
|
||||||
state.begin_fill(fill_color);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
TurtleCommand::PenUp => {
|
|
||||||
state.pen_down = false;
|
|
||||||
// Close current contour if filling
|
|
||||||
state.close_fill_contour();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
TurtleCommand::PenDown => {
|
|
||||||
state.pen_down = true;
|
|
||||||
// Start new contour if filling
|
|
||||||
state.start_fill_contour();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
TurtleCommand::EndFill => {
|
|
||||||
if let Some(mut fill_state) = state.filling.take() {
|
|
||||||
// Close final contour if it has vertices
|
|
||||||
if !fill_state.current_contour.is_empty() {
|
|
||||||
fill_state.contours.push(fill_state.current_contour);
|
|
||||||
}
|
|
||||||
// Create fill command - Lyon will handle EvenOdd automatically
|
|
||||||
if !fill_state.contours.is_empty() {
|
|
||||||
if let Ok(mesh_data) = tessellation::tessellate_multi_contour(
|
|
||||||
&fill_state.contours,
|
|
||||||
fill_state.fill_color,
|
|
||||||
) {
|
|
||||||
commands.push(DrawCommand::Mesh(mesh_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute command immediately
|
// Execute movement commands
|
||||||
let target_state = self.calculate_target_state(state, &command);
|
let target_state = self.calculate_target_state(state, &command);
|
||||||
*state = target_state.clone();
|
*state = target_state.clone();
|
||||||
|
|
||||||
// Record vertices after position update if filling
|
// Record fill vertices AFTER movement using centralized helper
|
||||||
match &command {
|
crate::execution::record_fill_vertices_after_movement(
|
||||||
TurtleCommand::Circle {
|
&command,
|
||||||
radius,
|
&start_state,
|
||||||
angle,
|
state,
|
||||||
steps,
|
|
||||||
direction,
|
|
||||||
} => {
|
|
||||||
// For circles, record multiple vertices along the arc
|
|
||||||
if state.filling.is_some() {
|
|
||||||
use crate::circle_geometry::CircleGeometry;
|
|
||||||
let geom = CircleGeometry::new(
|
|
||||||
start_state.position,
|
|
||||||
start_state.heading,
|
|
||||||
*radius,
|
|
||||||
*direction,
|
|
||||||
);
|
);
|
||||||
state.record_fill_vertices_for_arc(
|
|
||||||
geom.center,
|
|
||||||
*radius,
|
|
||||||
geom.start_angle_from_center,
|
|
||||||
angle.to_radians(),
|
|
||||||
*direction,
|
|
||||||
*steps as u32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TurtleCommand::Move(_) | TurtleCommand::Goto(_) => {
|
|
||||||
state.record_fill_vertex();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture end state AFTER executing this command
|
|
||||||
let end_state = state.clone();
|
let end_state = state.clone();
|
||||||
|
|
||||||
// Collect drawable commands with their individual start and end states
|
// Collect drawable commands
|
||||||
// Only create line drawing if pen is down
|
|
||||||
if Self::command_creates_drawing(&command) && start_state.pen_down {
|
if Self::command_creates_drawing(&command) && start_state.pen_down {
|
||||||
completed_commands.push((command, start_state, end_state));
|
completed_commands.push((command, start_state, end_state));
|
||||||
draw_call_count += 1;
|
draw_call_count += 1;
|
||||||
|
|
||||||
// Stop if we've reached the draw call limit for this frame
|
|
||||||
if draw_call_count >= max_draw_calls {
|
if draw_call_count >= max_draw_calls {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -269,97 +202,34 @@ impl TweenController {
|
|||||||
|
|
||||||
// Check if tween is finished (use heading_tweener as it's used by all commands)
|
// Check if tween is finished (use heading_tweener as it's used by all commands)
|
||||||
if tween.heading_tweener.is_finished() {
|
if tween.heading_tweener.is_finished() {
|
||||||
// Tween complete, finalize state
|
|
||||||
let start_state = tween.start_state.clone();
|
let start_state = tween.start_state.clone();
|
||||||
*state = tween.target_state.clone();
|
*state = tween.target_state.clone();
|
||||||
let end_state = state.clone();
|
let end_state = state.clone();
|
||||||
|
|
||||||
// Return the completed command and start/end states
|
|
||||||
let completed_command = tween.command.clone();
|
let completed_command = tween.command.clone();
|
||||||
self.current_tween = None;
|
self.current_tween = None;
|
||||||
|
|
||||||
// Handle fill commands that have side effects
|
// Execute side-effect-only commands using centralized helper
|
||||||
match &completed_command {
|
if crate::execution::execute_command_side_effects(
|
||||||
TurtleCommand::BeginFill => {
|
&completed_command,
|
||||||
let fill_color = state.fill_color.unwrap_or(macroquad::prelude::BLACK);
|
state,
|
||||||
state.begin_fill(fill_color);
|
commands,
|
||||||
// Don't return, continue to next command
|
|
||||||
return self.update(state, commands);
|
|
||||||
}
|
|
||||||
TurtleCommand::EndFill => {
|
|
||||||
if let Some(mut fill_state) = state.filling.take() {
|
|
||||||
// Close final contour if it has vertices
|
|
||||||
if !fill_state.current_contour.is_empty() {
|
|
||||||
fill_state.contours.push(fill_state.current_contour);
|
|
||||||
}
|
|
||||||
// Create fill command - Lyon will handle EvenOdd automatically
|
|
||||||
if !fill_state.contours.is_empty() {
|
|
||||||
if let Ok(mesh_data) = tessellation::tessellate_multi_contour(
|
|
||||||
&fill_state.contours,
|
|
||||||
fill_state.fill_color,
|
|
||||||
) {
|
) {
|
||||||
commands.push(DrawCommand::Mesh(mesh_data));
|
return self.update(state, commands); // Continue to next command
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Don't return, continue to next command
|
|
||||||
return self.update(state, commands);
|
|
||||||
}
|
|
||||||
TurtleCommand::Circle {
|
|
||||||
radius,
|
|
||||||
angle,
|
|
||||||
steps,
|
|
||||||
direction,
|
|
||||||
} => {
|
|
||||||
// For circles, record multiple vertices along the arc
|
|
||||||
if state.filling.is_some() {
|
|
||||||
use crate::circle_geometry::CircleGeometry;
|
|
||||||
let geom = CircleGeometry::new(
|
|
||||||
start_state.position,
|
|
||||||
start_state.heading,
|
|
||||||
*radius,
|
|
||||||
*direction,
|
|
||||||
);
|
|
||||||
state.record_fill_vertices_for_arc(
|
|
||||||
geom.center,
|
|
||||||
*radius,
|
|
||||||
geom.start_angle_from_center,
|
|
||||||
angle.to_radians(),
|
|
||||||
*direction,
|
|
||||||
*steps as u32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if Self::command_creates_drawing(&completed_command) && start_state.pen_down
|
// Record fill vertices for movement commands using centralized helper
|
||||||
{
|
crate::execution::record_fill_vertices_after_movement(
|
||||||
return vec![(completed_command, start_state, end_state)];
|
&completed_command,
|
||||||
} else {
|
&start_state,
|
||||||
// Movement but no drawing (pen up) - continue
|
state,
|
||||||
return self.update(state, commands);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
TurtleCommand::Move(_) | TurtleCommand::Goto(_) => {
|
|
||||||
// Movement commands: record vertex if filling
|
|
||||||
state.record_fill_vertex();
|
|
||||||
|
|
||||||
if Self::command_creates_drawing(&completed_command) && start_state.pen_down
|
|
||||||
{
|
|
||||||
return vec![(completed_command, start_state, end_state)];
|
|
||||||
} else {
|
|
||||||
// Movement but no drawing (pen up) - continue
|
|
||||||
return self.update(state, commands);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ if Self::command_creates_drawing(&completed_command)
|
|
||||||
&& start_state.pen_down =>
|
|
||||||
{
|
|
||||||
// Return drawable commands
|
// Return drawable commands
|
||||||
|
if Self::command_creates_drawing(&completed_command) && start_state.pen_down {
|
||||||
return vec![(completed_command, start_state, end_state)];
|
return vec![(completed_command, start_state, end_state)];
|
||||||
}
|
} else {
|
||||||
_ => {
|
return self.update(state, commands); // Continue to next command
|
||||||
// Non-drawable, non-fill commands - continue to next
|
|
||||||
return self.update(state, commands);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,47 +245,21 @@ impl TweenController {
|
|||||||
TurtleCommand::SetSpeed(new_speed) => {
|
TurtleCommand::SetSpeed(new_speed) => {
|
||||||
state.set_speed(*new_speed);
|
state.set_speed(*new_speed);
|
||||||
self.speed = *new_speed;
|
self.speed = *new_speed;
|
||||||
// If switched to instant mode, process commands immediately
|
|
||||||
if matches!(self.speed, AnimationSpeed::Instant(_)) {
|
if matches!(self.speed, AnimationSpeed::Instant(_)) {
|
||||||
return self.update(state, commands); // Recursively process in instant mode
|
|
||||||
}
|
|
||||||
// For animated mode speed changes, continue to next command
|
|
||||||
return self.update(state, commands);
|
return self.update(state, commands);
|
||||||
}
|
}
|
||||||
TurtleCommand::PenUp => {
|
|
||||||
state.pen_down = false;
|
|
||||||
state.close_fill_contour();
|
|
||||||
return self.update(state, commands);
|
return self.update(state, commands);
|
||||||
}
|
}
|
||||||
TurtleCommand::PenDown => {
|
_ => {
|
||||||
state.pen_down = true;
|
// Use centralized helper for side effects
|
||||||
state.start_fill_contour();
|
if crate::execution::execute_command_side_effects(
|
||||||
return self.update(state, commands);
|
&command_clone,
|
||||||
}
|
state,
|
||||||
TurtleCommand::BeginFill => {
|
commands,
|
||||||
let fill_color = state.fill_color.unwrap_or(macroquad::prelude::BLACK);
|
|
||||||
state.begin_fill(fill_color);
|
|
||||||
return self.update(state, commands);
|
|
||||||
}
|
|
||||||
TurtleCommand::EndFill => {
|
|
||||||
if let Some(mut fill_state) = state.filling.take() {
|
|
||||||
// Close final contour if it has vertices
|
|
||||||
if !fill_state.current_contour.is_empty() {
|
|
||||||
fill_state.contours.push(fill_state.current_contour);
|
|
||||||
}
|
|
||||||
// Create fill command - Lyon will handle EvenOdd automatically
|
|
||||||
if !fill_state.contours.is_empty() {
|
|
||||||
if let Ok(mesh_data) = tessellation::tessellate_multi_contour(
|
|
||||||
&fill_state.contours,
|
|
||||||
fill_state.fill_color,
|
|
||||||
) {
|
) {
|
||||||
commands.push(DrawCommand::Mesh(mesh_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return self.update(state, commands);
|
return self.update(state, commands);
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let speed = state.speed; // Extract speed before borrowing self
|
let speed = state.speed; // Extract speed before borrowing self
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user