improve circle construction and rendering
This commit is contained in:
parent
998cffdcbf
commit
96b02f61be
@ -3,6 +3,41 @@
|
|||||||
use crate::general::Radians;
|
use crate::general::Radians;
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
|
/// Generate evenly-spaced points along a circular arc.
|
||||||
|
///
|
||||||
|
/// Returns exactly `steps` points, uniformly distributed from (not including)
|
||||||
|
/// the arc start to (including) the arc end. This is the **single source of
|
||||||
|
/// truth** for arc sampling used by tessellation, tween stroke drawing, and
|
||||||
|
/// fill-polygon preview.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `center` — centre of the circle
|
||||||
|
/// * `radius` — arc radius
|
||||||
|
/// * `start_angle` — angle from `center` to the turtle's start position (radians)
|
||||||
|
/// * `sweep_angle` — total arc sweep in radians (absolute; sign comes from `direction`)
|
||||||
|
/// * `steps` — number of sample points (clamped to ≥ 1)
|
||||||
|
/// * `direction` — which way the arc curves
|
||||||
|
pub(crate) fn arc_points(
|
||||||
|
center: Vec2,
|
||||||
|
radius: f32,
|
||||||
|
start_angle: f32,
|
||||||
|
sweep_angle: f32,
|
||||||
|
steps: usize,
|
||||||
|
direction: CircleDirection,
|
||||||
|
) -> Vec<Vec2> {
|
||||||
|
let n = steps.max(1);
|
||||||
|
let step_size = sweep_angle / n as f32;
|
||||||
|
(1..=n)
|
||||||
|
.map(|i| {
|
||||||
|
let a = match direction {
|
||||||
|
CircleDirection::Left => start_angle - step_size * i as f32,
|
||||||
|
CircleDirection::Right => start_angle + step_size * i as f32,
|
||||||
|
};
|
||||||
|
Vec2::new(center.x + radius * a.cos(), center.y + radius * a.sin())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Direction of circular motion (in screen coordinates with Y-down)
|
/// Direction of circular motion (in screen coordinates with Y-down)
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum CircleDirection {
|
pub enum CircleDirection {
|
||||||
|
|||||||
@ -176,41 +176,32 @@ pub(crate) fn render_world_with_tweens(world: &TurtleWorld, zoom_level: f32) {
|
|||||||
} = &tween.command
|
} = &tween.command
|
||||||
{
|
{
|
||||||
// Calculate partial arc vertices based on current progress
|
// Calculate partial arc vertices based on current progress
|
||||||
use crate::circle_geometry::CircleGeometry;
|
use crate::circle_geometry::{arc_points, CircleGeometry};
|
||||||
use crate::general::Radians;
|
use crate::general::Radians;
|
||||||
let geom = CircleGeometry::new(
|
let geom = CircleGeometry::new(
|
||||||
tween.start_params.position,
|
tween.start_params.position,
|
||||||
Radians::new(tween.start_params.heading),
|
Radians::new(tween.start_params.heading),
|
||||||
*radius,
|
*radius,
|
||||||
*direction,
|
*direction,
|
||||||
); // Calculate progress
|
);
|
||||||
let elapsed = get_time() - tween.start_time;
|
let elapsed = get_time() - tween.start_time;
|
||||||
let progress = (elapsed / tween.duration).min(1.0);
|
let progress = (elapsed / tween.duration).min(1.0);
|
||||||
let eased_progress = CubicInOut.tween(1.0, progress as f32);
|
let eased_progress = CubicInOut.tween(1.0, progress as f32);
|
||||||
|
|
||||||
// Generate arc vertices for the partial arc
|
// Delegate to the shared arc_points function — same sampling
|
||||||
let num_samples = *steps.max(&1);
|
// strategy as tessellate_arc, eliminating the divergence.
|
||||||
let samples_to_draw =
|
let samples_to_draw =
|
||||||
((num_samples as f32 * eased_progress) as usize).max(1);
|
(((*steps).max(1) as f32 * eased_progress) as usize).max(1);
|
||||||
|
let sweep_so_far = angle.as_radians().value() * eased_progress;
|
||||||
for i in 1..=samples_to_draw {
|
for pt in arc_points(
|
||||||
let sample_progress = i as f32 / num_samples as f32;
|
geom.center,
|
||||||
let current_angle = match direction {
|
*radius,
|
||||||
crate::circle_geometry::CircleDirection::Left => {
|
geom.start_angle_from_center,
|
||||||
geom.start_angle_from_center
|
sweep_so_far,
|
||||||
- angle.as_radians().value() * sample_progress
|
samples_to_draw,
|
||||||
}
|
*direction,
|
||||||
crate::circle_geometry::CircleDirection::Right => {
|
) {
|
||||||
geom.start_angle_from_center
|
current_preview.push(pt);
|
||||||
+ angle.as_radians().value() * sample_progress
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let vertex = Vec2::new(
|
|
||||||
geom.center.x + radius * current_angle.cos(),
|
|
||||||
geom.center.y + radius * current_angle.sin(),
|
|
||||||
);
|
|
||||||
current_preview.push(vertex);
|
|
||||||
}
|
}
|
||||||
} else if matches!(
|
} else if matches!(
|
||||||
&tween.command,
|
&tween.command,
|
||||||
|
|||||||
@ -69,14 +69,15 @@ pub mod svg_export {
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
use crate::circle_geometry::CircleGeometry;
|
use crate::circle_geometry::CircleGeometry;
|
||||||
|
use crate::general::Radians;
|
||||||
let geom = CircleGeometry::new(
|
let geom = CircleGeometry::new(
|
||||||
source.start_position,
|
source.start_position,
|
||||||
source.start_heading,
|
Radians::new(source.start_heading),
|
||||||
*radius,
|
*radius,
|
||||||
*direction,
|
*direction,
|
||||||
);
|
);
|
||||||
let center = geom.center;
|
let center = geom.center;
|
||||||
if (*angle - 360.0).abs() < 1e-3 {
|
if (angle.value() - 360.0).abs() < 1e-3 {
|
||||||
// Voller Kreis
|
// Voller Kreis
|
||||||
update_bounds(
|
update_bounds(
|
||||||
&mut min_x,
|
&mut min_x,
|
||||||
@ -123,7 +124,7 @@ pub mod svg_export {
|
|||||||
center.x + radius,
|
center.x + radius,
|
||||||
center.y + radius,
|
center.y + radius,
|
||||||
);
|
);
|
||||||
let large_arc = if *angle > 180.0 { 1 } else { 0 };
|
let large_arc = if angle.value() > 180.0 { 1 } else { 0 };
|
||||||
let sweep = match direction {
|
let sweep = match direction {
|
||||||
crate::circle_geometry::CircleDirection::Left => 0,
|
crate::circle_geometry::CircleDirection::Left => 0,
|
||||||
crate::circle_geometry::CircleDirection::Right => 1,
|
crate::circle_geometry::CircleDirection::Right => 1,
|
||||||
|
|||||||
@ -309,40 +309,32 @@ pub(crate) fn tessellate_arc(
|
|||||||
segments: usize,
|
segments: usize,
|
||||||
direction: crate::circle_geometry::CircleDirection,
|
direction: crate::circle_geometry::CircleDirection,
|
||||||
) -> Result<MeshData, Box<dyn std::error::Error>> {
|
) -> Result<MeshData, Box<dyn std::error::Error>> {
|
||||||
// Build arc path manually from segments
|
use crate::circle_geometry::arc_points;
|
||||||
let mut builder = Path::builder();
|
|
||||||
|
|
||||||
let start_angle = start_angle_degrees.to_radians();
|
let start_angle = start_angle_degrees.to_radians();
|
||||||
let arc_angle = arc_angle_degrees.to_radians();
|
let sweep_angle = arc_angle_degrees.to_radians();
|
||||||
let step = arc_angle / segments as f32;
|
|
||||||
|
|
||||||
// Calculate first point
|
let mut builder = Path::builder();
|
||||||
let first_point = point(
|
|
||||||
|
// Start point of the arc (arc_points returns everything *after* this)
|
||||||
|
builder.begin(point(
|
||||||
center.x + radius * start_angle.cos(),
|
center.x + radius * start_angle.cos(),
|
||||||
center.y + radius * start_angle.sin(),
|
center.y + radius * start_angle.sin(),
|
||||||
);
|
));
|
||||||
builder.begin(first_point);
|
|
||||||
|
|
||||||
// Add remaining points - direction matters!
|
// Remaining points — single source of truth for arc sampling
|
||||||
for i in 1..=segments {
|
for pt in arc_points(
|
||||||
let angle = match direction {
|
center,
|
||||||
crate::circle_geometry::CircleDirection::Left => {
|
radius,
|
||||||
// Counter-clockwise: subtract angle
|
start_angle,
|
||||||
start_angle - step * i as f32
|
sweep_angle,
|
||||||
}
|
segments,
|
||||||
crate::circle_geometry::CircleDirection::Right => {
|
direction,
|
||||||
// Clockwise: add angle
|
) {
|
||||||
start_angle + step * i as f32
|
builder.line_to(point(pt.x, pt.y));
|
||||||
}
|
|
||||||
};
|
|
||||||
let pt = point(
|
|
||||||
center.x + radius * angle.cos(),
|
|
||||||
center.y + radius * angle.sin(),
|
|
||||||
);
|
|
||||||
builder.line_to(pt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.end(false); // Don't close the arc
|
builder.end(false); // open arc, not a closed polygon
|
||||||
let path = builder.build();
|
let path = builder.build();
|
||||||
|
|
||||||
// Tessellate stroke
|
// Tessellate stroke
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user