turtle/turtle-lib-macroquad/examples/sierpinski_triangle.rs
2025-10-12 17:30:49 +02:00

99 lines
3.5 KiB
Rust

//! Draws a Sierpiński triangle with automatic positioning and sizing.
//!
//! The Sierpiński triangle is a fairly simple self-similar fractal geometric shape: it consists of
//! many nested equilateral triangles. More formally, such a triangle is itself three triangles of
//! one level below and a size divided by two. Level zero means a simple equilateral triangle. The
//! drawing procedure is as follows, for a given level and size:
//!
//! * If level is 0
//! * Draw an equilateral triangle of the given size.
//! * otherwise
//! * Draw the half-sized level - 1 triangle at the bottom left.
//! * Go the start of the bottom-right slot.
//! * Draw a half-sized level - 1 triangle.
//! * Go to the start of the top slot.
//! * Draw a half-sized level - 1 triangle.
//!
//! That is relatively easy to implement, as long as you follow these steps and let recursion do
//! the rest. Another little bonus this example provides is the ability to customize the drawing
//! size: the triangle will stay correctly sized and positioned automatically.
use macroquad::window::{screen_height, screen_width};
use turtle_lib_macroquad::*;
/// The number of levels to draw following the recursive procedure.
const LEVELS: u8 = 9;
/// Triangle size (adjust to fit nicely in window)
const TRIANGLE_SIZE: f32 = 300.0;
#[turtle_main("Sierpiński Triangle")]
fn draw_sierpinski(turtle: &mut TurtlePlan) {
turtle.set_speed(1500); // Fast drawing
turtle.set_pen_width(0.2);
// Auto-sized procedure
sierpinski_triangle_auto(turtle, LEVELS);
// Hide turtle when done drawing in order to fully reveal the result
turtle.hide();
}
/// Recursive function drawing a Sierpiński triangle.
///
/// It will do it with the given `turtle` and start at its current position and heading. `level`
/// is the depth of the drawing to be done, zero meaning a simple triangle. `size` is the length
/// of the outermost triangle's sides.
fn sierpinski_triangle(turtle: &mut TurtlePlan, level: u8, size: f32) {
// When level 0 is reached, just draw an equilateral triangle.
if level == 0 {
turtle.pen_down();
for _ in 0..3 {
turtle.forward(size);
turtle.left(120.0);
}
turtle.pen_up();
} else {
// Parameters for subsequent calls are the same.
let next_level = level - 1;
let next_size = size / 2.0;
// Bottom-left triangle.
sierpinski_triangle(turtle, next_level, next_size);
turtle.forward(next_size);
// Bottom-right triangle.
sierpinski_triangle(turtle, next_level, next_size);
turtle.left(120.0);
turtle.forward(next_size);
turtle.right(120.0);
// Top triangle.
sierpinski_triangle(turtle, next_level, next_size);
// Go back to the start.
turtle.right(120.0);
turtle.forward(next_size);
turtle.left(120.0);
}
}
/// Draws a Sierpiński triangle with automatic size and start point.
///
/// `level` is still required, it can't be computed automatically. However, given the used
/// canvas size, it will compute the appropriate size and start point so the triangle gets
/// centered and occupies as much drawing space as possible while staying in bounds.
fn sierpinski_triangle_auto(turtle: &mut TurtlePlan, level: u8) {
let size = TRIANGLE_SIZE;
turtle.pen_up();
turtle.go_to((-screen_width() / 2.0 + 20.0, screen_height() / 2.0 - 20.0));
turtle.set_heading(0.0); // 0 = East (pointing right)
// The drawing itself.
sierpinski_triangle(turtle, level, size);
}