Implement tessellation for turtle graphics with fill support

- Added tessellation module to handle path tessellation using Lyon.
- Updated execution logic to record fill vertices and manage fill contours.
- Integrated tessellation into command execution for lines, arcs, and filled shapes.
- Enhanced TurtleState to track fill state and contours.
- Modified TweenController to handle fill commands and update drawing commands accordingly.
- Improved debug output for fill operations and tessellation processes.
This commit is contained in:
2025-10-12 12:34:20 +02:00
parent c3f5136359
commit 00b9007f00
17 changed files with 1808 additions and 443 deletions
+97
View File
@@ -0,0 +1,97 @@
//! Cheese example - demonstrates multi-contour fills with holes
//!
//! This example creates a cheese-like shape by:
//! 1. Drawing the outer square boundary
//! 2. Lifting the pen (pen_up) to close that contour
//! 3. Drawing circular and triangular holes with pen_up/pen_down
//!
//! Lyon's EvenOdd fill rule automatically creates holes where contours overlap!
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Cheese with Holes")]
async fn main() {
let mut turtle = create_turtle();
// Set fill color to yellow (cheese color!)
turtle.set_fill_color(YELLOW);
turtle.set_pen_color(ORANGE);
turtle.set_pen_width(3.0);
println!("=== Starting cheese fill ===");
turtle.begin_fill();
// Draw outer boundary (large square)
println!("Drawing outer square boundary...");
for _ in 0..4 {
turtle.forward(400.0);
turtle.right(90.0);
}
// Close outer contour and start drawing holes
println!("Closing outer contour with pen_up");
turtle.pen_up();
// Draw triangular hole in the middle
println!("Drawing triangular hole...");
turtle.go_to(vec2(200.0, 120.0));
turtle.pen_down(); // Start new contour for hole
for _ in 0..3 {
turtle.forward(160.0);
turtle.right(120.0);
}
println!("Closing triangle contour with pen_up");
turtle.pen_up(); // Close triangle hole contour
// Draw circular hole (top-left) using circle_left
println!("Drawing circular hole (top-left) with circle_left...");
turtle.go_to(vec2(100.0, 100.0));
turtle.pen_down(); // Start new contour for hole
turtle.circle_left(30.0, 360.0, 36); // radius=30, full circle, 36 steps
println!("Closing circle contour with pen_up");
turtle.pen_up(); // Close circle hole contour
// Draw circular hole (bottom-right) using circle_right
println!("Drawing circular hole (bottom-right) with circle_right...");
turtle.go_to(vec2(280.0, 280.0));
turtle.pen_down(); // Start new contour for hole
turtle.circle_right(40.0, 360.0, 36); // radius=40, full circle, 36 steps
println!("Closing circle contour with pen_up");
turtle.pen_up(); // Close circle hole contour
// End fill - Lyon will automatically create holes!
println!("Calling end_fill - Lyon should create holes now!");
turtle.end_fill();
// Set animation speed
turtle.set_speed(300);
println!("Building and executing turtle plan...");
// Execute the plan
let mut app = TurtleApp::new().with_commands(turtle.build());
loop {
clear_background(Color::new(0.95, 0.95, 0.98, 1.0));
app.update();
app.render();
// Instructions
draw_text(
"Cheese with Holes - pen_up/pen_down creates multiple contours!",
10.0,
20.0,
18.0,
BLACK,
);
draw_text("Press ESC or Q to quit", 10.0, 40.0, 16.0, DARKGRAY);
if is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::Q) {
break;
}
next_frame().await;
}
}
+5 -3
View File
@@ -10,7 +10,7 @@ async fn main() {
plan.shape(ShapeType::Turtle);
// Draw some circles
plan.set_color(RED);
plan.set_pen_color(RED);
plan.set_pen_width(0.5);
plan.left(90.0);
plan.set_speed(999);
@@ -18,14 +18,16 @@ async fn main() {
plan.forward(150.0);
plan.set_speed(100);
plan.set_color(BLUE);
plan.set_pen_color(BLUE);
plan.circle_right(50.0, 270.0, 72); // partial circle to the right
// Set animation speed
plan.set_speed(20);
plan.forward(150.0);
plan.circle_left(50.0, 180.0, 12);
plan.circle_right(50.0, 180.0, 12);
plan.set_speed(700);
plan.set_color(GREEN);
plan.set_pen_color(GREEN);
plan.circle_left(50.0, 180.0, 36); // Half circle to the left
// Create turtle app with animation (speed = 100 pixels/sec)
@@ -0,0 +1,157 @@
//! Advanced fill example with multiple holes and complex shapes
use macroquad::{miniquad::window::set_window_size, prelude::*};
use turtle_lib_macroquad::*;
#[macroquad::main("Advanced Fill Demo")]
async fn main() {
set_window_size(2000, 1900);
let mut t = create_turtle();
// Example 1: Star shape (concave polygon)
t.pen_up();
t.go_to(vec2(-200.0, 100.0));
t.pen_down();
t.set_heading(0.0);
t.set_fill_color(GOLD);
t.set_pen_color(ORANGE);
t.set_pen_width(2.0);
t.set_speed(500);
t.begin_fill();
// Draw 5-pointed star
for _ in 0..5 {
t.forward(100.0);
t.right(144.0);
}
t.end_fill();
// Example 2: Swiss cheese (polygon with multiple holes)
t.pen_up();
t.go_to(vec2(100.0, 100.0));
t.pen_down();
t.set_heading(0.0);
t.set_fill_color(YELLOW);
t.set_pen_color(ORANGE);
t.begin_fill();
// Outer square
for _ in 0..4 {
t.forward(150.0);
t.right(90.0);
}
// First hole (circle)
t.pen_up();
t.go_to(vec2(140.0, 130.0));
t.pen_down();
t.circle_right(150.0, 360.0, 36);
// Second hole (circle)
t.pen_up();
t.go_to(vec2(200.0, 170.0));
t.pen_down();
t.circle_right(10.0, 360.0, 36);
// Third hole (triangle)
t.pen_up();
t.go_to(vec2(160.0, 200.0));
t.pen_down();
t.circle_right(15.0, 360.0, 3);
// Fourth hole (square)
t.pen_up();
t.go_to(vec2(190.0, 200.0));
t.pen_down();
t.circle_right(15.0, 360.0, 4);
// fifth hole (pentagon)
t.pen_up();
t.go_to(vec2(230.0, 200.0));
t.pen_down();
t.circle_right(15.0, 360.0, 5);
t.end_fill();
// Example 3: Donut (circle with circular hole)
t.pen_up();
t.go_to(vec2(-100.0, -100.0));
t.pen_down();
t.set_heading(0.0);
t.set_fill_color(Color::new(0.8, 0.4, 0.2, 1.0));
t.set_pen_color(Color::new(0.6, 0.3, 0.1, 1.0));
t.begin_fill();
// Outer circle
for _ in 0..72 {
t.forward(3.0);
t.right(5.0);
}
// Move to inner circle
t.pen_up();
t.go_to(vec2(-75.0, -90.0));
t.pen_down();
// Inner circle (hole)
for _ in 0..72 {
t.forward(1.5);
t.right(5.0);
}
t.end_fill();
// Set animation speed
t.set_speed(500);
let mut app = TurtleApp::new().with_commands(t.build());
let target_fps = 1.0; // 1 frame per second for debugging
let frame_time = 1.0 / target_fps;
let mut last_frame_time = macroquad::time::get_time();
loop {
// Frame rate limiting
let current_time = macroquad::time::get_time();
let delta = current_time - last_frame_time;
if delta < frame_time {
// std::thread::sleep(std::time::Duration::from_secs_f64(frame_time - delta));
}
last_frame_time = macroquad::time::get_time();
clear_background(Color::new(0.95, 0.95, 0.98, 1.0));
app.update();
app.render();
// Instructions
draw_text(
"Advanced Fill Demo: Star, Swiss Cheese, Donut",
10.0,
20.0,
20.0,
BLACK,
);
draw_text(
"Features: concave polygons, multiple holes, pen_up during fill",
10.0,
40.0,
16.0,
DARKGRAY,
);
draw_text(
"Mouse: drag to pan, scroll to zoom",
10.0,
60.0,
16.0,
DARKGRAY,
);
next_frame().await
}
}
@@ -0,0 +1,55 @@
//! Fill demonstration with holes
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Fill Demo")]
async fn main() {
let mut t = create_turtle();
// Example from requirements: circle with hole (like a donut)
t.set_pen_color(BLUE);
t.set_pen_width(3.0);
t.right(90.0);
// Set fill color and begin fill
t.set_fill_color(RED);
t.begin_fill();
// Outer circle
t.circle_right(150.0, 360.0, 72);
// Move to start of inner circle (hole)
// pen_up doesn't matter for fill - vertices still recorded!
t.pen_up();
t.forward(50.0);
t.pen_down();
// Inner circle (creates a hole)
t.circle_right(150.0, 360.0, 72);
t.end_fill();
// Draw a square with no fill
t.pen_up();
t.forward(100.0);
t.pen_down();
t.set_pen_color(GREEN);
for _ in 0..4 {
t.forward(100.0);
t.right(90.0);
}
// Set animation speed
t.set_speed(100);
let mut app = TurtleApp::new().with_commands(t.build());
loop {
clear_background(WHITE);
app.update();
app.render();
next_frame().await
}
}
@@ -0,0 +1,66 @@
//! Example matching the original requirements exactly
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Fill Example - Original Requirements")]
async fn main() {
let mut turtle = create_turtle();
turtle.right(90.0);
turtle.set_pen_width(3.0);
turtle.set_speed(900);
turtle.set_pen_color(BLUE);
turtle.set_fill_color(RED);
turtle.begin_fill();
turtle.circle_left(100.0, 360.0, 16);
// Draw a circle (36 small steps)
for _ in 0..36 {
turtle.forward(5.0);
turtle.right(10.0);
}
turtle.end_fill();
// Draw a square with no fill
turtle.set_pen_color(GREEN);
turtle.forward(120.0);
for _ in 0..3 {
turtle.right(90.0);
turtle.forward(240.0);
}
turtle.right(90.0);
turtle.forward(120.0);
// Set speed for animation
turtle.set_speed(200);
let mut app = TurtleApp::new().with_commands(turtle.build());
loop {
clear_background(WHITE);
app.update();
app.render();
// Instructions
draw_text(
"Fill Example - Circle filled with red, square not filled",
10.0,
20.0,
20.0,
BLACK,
);
draw_text(
"Mouse: drag to pan, scroll to zoom",
10.0,
40.0,
16.0,
DARKGRAY,
);
next_frame().await
}
}
+3
View File
@@ -7,8 +7,10 @@ use turtle_lib_macroquad::*;
async fn main() {
// Create a turtle plan
let mut t = create_turtle();
t.set_speed(900);
t.circle_left(90.0, 180.0, 36);
t.begin_fill();
t.circle_left(90.0, 180.0, 36);
t.circle_left(45.0, 180.0, 26);
t.circle_right(45.0, 180.0, 26);
@@ -24,6 +26,7 @@ async fn main() {
t.left(90.0);
t.pen_down();
t.circle_right(8.0, 360.0, 12);
t.end_fill();
// Set animation speed
t.set_speed(1000);