turtle/README.md

12 KiB

Turtle Graphics Library

A modern turtle graphics library for Rust built on Macroquad with Lyon for high-quality GPU-accelerated rendering.

Project Status

Stable - Complete Lyon integration with multi-contour fill system and live animation preview.

Features

  • 🎨 Simple Builder API: Chain commands like forward(100).right(90)
  • Smooth Animations: Tweening support with easing functions and live fill preview
  • 🚀 Instant Mode: Execute commands immediately without animation (speed ≥ 999)
  • 🎯 High-Quality Rendering: Complete Lyon tessellation pipeline with GPU acceleration
  • 🫟 Multi-Contour Fills: Automatic hole detection with EvenOdd fill rule - draw cheese with holes!
  • 📐 Self-Intersecting Paths: Stars, complex shapes - all handled correctly
  • 🐢 Multiple Turtle Shapes: Triangle, classic turtle, circle, square, arrow, and custom shapes
  • 🔍 Structured Logging: Optional tracing integration for debugging (zero overhead when disabled)
  • 💨 Lightweight: Fast compilation and runtime
  • 📤 SVG Export: Export drawings to SVG format with viewBox and padding (feature-gated)

Quick Start

The simplest example to draw a square:

//! Minimal turtle example - just 10 lines!
//!
//! This is the simplest possible turtle program using the macro.

use turtle_lib::*;

#[turtle_main]
fn hello() {
    turtle.set_pen_color(BLUE);
    for _ in 0..4 {
        turtle.forward(100.0);
        turtle.right(90.0);
    }
}

The turtle starts at the center of the window, facing right (0 degrees). The above code draws a blue square.

The turtle_main macro sets up the Macroquad window, turtle initialization, and main loop for you. It expands to code similar to this:

use macroquad::prelude::*;
use turtle_lib::*;

#[macroquad::main("Turtle")]
async fn main() {
    // Create a turtle plan
    let mut plan = create_turtle();
    
    // Set speed (part of the plan)
    plan.set_speed(100);
    
    // Draw a square
    for _ in 0..4 {
        plan.forward(100).right(90);
    }
    
    // Create app (speed is managed by commands)
    let mut app = TurtleApp::new().with_commands(plan.build());
    
    loop {
        clear_background(WHITE);
        app.update();
        app.render();
        next_frame().await
    }
}

API Overview

Creating Plans

let mut plan = create_turtle();

// Movement
plan.forward(100);
plan.backward(50);

// Rotation
plan.left(90);    // degrees
plan.right(45);

// Circular arcs
plan.circle_left(50.0, 180.0, 36);   // radius, angle (degrees), segments
plan.circle_right(50.0, 180.0, 36);  // draws arc to the right

// Pen control
plan.pen_up();
plan.pen_down();

// Filling (with automatic hole detection)
plan.set_fill_color(BLUE);
plan.begin_fill();
// ... draw shape ...
plan.end_fill();  // Auto-closes and applies fill

// Appearance
plan.set_color(RED);
plan.set_pen_width(5.0);
plan.hide();
plan.show();

// Speed control (dynamic)
plan.set_speed(100);  // Animated mode (< 999)
plan.set_speed(1000); // Instant mode (>= 999)

// Turtle shapes
plan.shape(ShapeType::Triangle);
plan.shape(ShapeType::Turtle);    // Classic turtle shape
plan.shape(ShapeType::Circle);
plan.shape(ShapeType::Square);
plan.shape(ShapeType::Arrow);

// Custom shapes
let custom = TurtleShape::new(
    vec![vec2(10.0, 0.0), vec2(-5.0, 5.0), vec2(-5.0, -5.0)],
    true  // filled
);
plan.set_shape(custom);

// Method chaining
plan.forward(100).right(90).forward(50);

Execution Modes

Speed controlled via commands, allowing dynamic switching during execution:

let mut plan = create_turtle();

// Fast initial positioning (instant mode)
plan.set_speed(1000);
plan.pen_up();
plan.goto(vec2(-100.0, -100.0));

// Slow animated drawing
plan.set_speed(50);
plan.pen_down();
plan.forward(200);
plan.right(90);

// Create app
let app = TurtleApp::new().with_commands(plan.build());

Speed Modes:

  • Speed < 999: Animated mode with smooth tweening
  • Speed >= 999: Instant mode (no animation) the bigger the number the more segments will be added per frame.
  • Default speed is 100.0 if not specified

Debugging and Logging

The library uses tracing for structured diagnostic logging. This is completely optional - if you don't set up a subscriber, there's zero overhead.

Enable Logging

// Add to your Cargo.toml:
// tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }

tracing_subscriber::fmt()
    .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
    .init();

Control verbosity with the RUST_LOG environment variable:

# Show debug output
RUST_LOG=turtle_lib=debug cargo run

# Very verbose trace output
RUST_LOG=turtle_lib=trace cargo run

See the complete example: examples/logging_example.rs demonstrates initialization, log levels, filtering, and example output.

SVG Export

Export your turtle drawings to SVG format for use in web applications, vector graphics editors, or further processing.

Enabling SVG Export

Add the svg feature to enable SVG export functionality:

cargo run --example export_svg --features svg

Usage

use turtle_lib::*;

// Create your drawing
let mut plan = create_turtle();
plan.forward(100).right(90).forward(100);

// Create app
let mut app = TurtleApp::new().with_commands(plan.build());

// Export to SVG
app.export_drawing("drawing.svg", export::DrawingFormat::Svg)?;

Features

  • Complete Primitive Support: Lines, circles, arcs, polygons, and fills
  • Automatic viewBox: Includes 20px padding around the entire drawing
  • Color and Styling: Preserves colors, pen width, and fill colors
  • Multi-Contour Fills: Exports complex fills with holes using SVG paths
  • Text Support: Exports text elements with positioning

Example

See examples/export_svg.rs for a complete example that draws various shapes and exports them to SVG.

The exported SVG can be opened in any web browser or vector graphics application.

Examples

Run examples with:

cargo run --example square
cargo run --example koch
cargo run --example shapes
cargo run --example yinyang
cargo run --example stern
cargo run --example nikolaus

# SVG export example (requires --features svg)
cargo run --example export_svg --features svg

# Logging example - shows how to enable debug output
cargo run --example logging_example
RUST_LOG=turtle_lib=debug cargo run --example logging_example

Available Examples

Basic Drawing

  • square.rs: Basic square drawing
  • koch.rs: Koch snowflake fractal
  • shapes.rs: Demonstrates different turtle shapes
  • stern.rs: Star pattern drawing
  • nikolaus.rs: Nikolaus (Santa) drawing

Fill Examples

  • yinyang.rs: Yin-yang symbol with automatic hole detection
  • fill_demo.rs: Donut shape with hole
  • fill_requirements.rs: Circle with red fill
  • fill_advanced.rs: Complex shapes (star, swiss cheese, multiple holes)
  • fill_circle_test.rs: Circle fills with different angles
  • fill_instant_test.rs: Quick fill test in instant mode

Export

  • export_svg.rs: Demonstrates SVG export functionality (requires --features svg)

Debugging

  • logging_example.rs: Demonstrates how to enable and use tracing/logging output

Why Lyon?

  • Automatic hole detection via EvenOdd fill rule
  • Handles any self-intersecting path
  • GPU-accelerated rendering
  • Standards-compliant (matches SVG, HTML Canvas)

Basic Fill

let mut plan = create_turtle();
plan.set_fill_color(RED);
plan.begin_fill();

// Draw shape
for _ in 0..4 {
    plan.forward(100);
    plan.right(90);
}

plan.end_fill();  // Auto-closes and fills

Fill with Holes (Multi-Contour)

plan.set_fill_color(BLUE);
plan.begin_fill();

// Outer circle (first contour)
plan.circle_left(90.0, 360.0, 72);

// pen_up() closes current contour
plan.pen_up();
plan.goto(vec2(0.0, -30.0));

// pen_down() starts new contour
plan.pen_down();

// Inner circle (becomes a hole automatically with EvenOdd rule!)
plan.circle_left(30.0, 360.0, 36);

plan.end_fill();  // Auto-detects holes and fills correctly

Fill Features

  • Live Preview - See fills progressively during animation
  • Auto-Close - Automatically connects end point to start on end_fill()
  • Multi-Contour - pen_up() closes contour, pen_down() opens next one
  • Automatic Hole Detection - EvenOdd fill rule handles any complexity
  • Self-Intersecting Paths - Stars and complex shapes work perfectly

Architecture

Module Structure

turtle-lib/src/
├── lib.rs          - Public API and TurtleApp
├── state.rs        - TurtleState and TurtleWorld
├── commands.rs     - TurtleCommand enum (consolidated commands)
├── builders.rs     - Builder traits (DirectionalMovement, Turnable, etc.)
├── execution.rs    - Command execution with fill support
├── tweening.rs     - Animation/tweening controller with dynamic speed
├── drawing.rs      - Rendering with Lyon tessellation
├── shapes.rs       - Turtle shape definitions
├── tessellation.rs - Lyon tessellation utilities
├── circle_geometry.rs - Circle arc calculations
└── general/        - Type definitions (Angle, Length, etc.)

Workspace Structure

turtlers/
├── turtle-lib/         - Main library (Macroquad + Lyon)
└── turtle-lib-macros/  - Procedural macros (turtle_main)

Building and Running

# Check all packages
cargo check

# Run specific example
cargo run --example yinyang

# Run SVG export example (requires svg feature)
cargo run --example export_svg --features svg

# Build release version
cargo build --release

# Build with SVG support
cargo build --features svg

Development Status

Completed

  • Turtle movement and rotation (consolidated Move/Turn commands)
  • Pen control (up/down) with contour management
  • Color and pen width
  • Circle arcs (left/right with unified Circle command)
  • Dynamic speed control via SetSpeed commands
  • Instant mode (speed ≥ 999) and animated mode (speed < 999)
  • Multi-contour fill system with automatic hole detection
  • Lyon integration for all drawing primitives
  • Multiple turtle shapes with custom shape support
  • Tweening system with easing functions
  • EvenOdd fill rule for complex self-intersecting paths
  • Live fill preview during animation with progressive rendering
  • Multi-contour support - pen_up/pen_down manage contours
  • Command consolidation
  • SVG Export - Export drawings to SVG with viewBox and padding (feature-gated)

What's New

Complete Lyon Migration

All drawing operations now use GPU-accelerated Lyon tessellation:

  • Unified pipeline: Lines, arcs, circles, and fills - all use the same high-quality rendering
  • Simplified codebase: ~410 lines of code eliminated
  • Better performance: GPU tessellation is faster than CPU-based primitives
  • Consistent quality: No more mixed rendering approaches

Multi-Contour Fill System 🕳️

Advanced fill capabilities with automatic hole detection:

  • EvenOdd fill rule: Draw shapes with holes - works like SVG and HTML Canvas
  • Pen state management: pen_up() closes contour, pen_down() opens next
  • Live preview: See fills progressively during animations
  • Self-intersecting paths: Stars, complex shapes - all handled correctly

Architectural Improvements 🏗️

  • Command consolidation: Unified Move/Turn/Circle commands (~250 lines eliminated)
  • Dynamic speed control: Change speed during execution via commands
  • Live animation preview: Progressive fill rendering during circle/arc drawing

License

MIT OR Apache-2.0

Contributing

Contributions are welcome! The library now has a stable foundation with complete Lyon integration and multi-contour fill support.