#![allow(non_snake_case)]
use std::fmt;
use super::{ShapeTrait, ShapeFinished};
use crate::draw_commands::DrawCommand;
use crate::color::Color;
use crate::ShapeType;
use crate::point::Point;
fn thirds(a: Point, b: Point) -> (Point, Point) {
let v = b - a;
(v*1.0/3.0 + a, v*2.0/3.0 + a)
}
fn bisector(A: Point, B: Point, C: Point) -> Point {
let Ap = (A - B) * -1.0 + B;
let (a, b, c) = (B.distance(C), A.distance(C), A.distance(B));
let incenter = (Ap * a + B * b + C * c) / (a + b + c);
let vec = incenter - B;
vec / vec.magnitude()
}
fn add_smooth(sec: Option<PathCommand>, last: PathCommand, pos: Point) -> (PathCommand, Option<PathCommand>) {
match last {
PathCommand::MoveTo(p) | PathCommand::LineTo(p) => {
if p == pos {
(last, None)
} else {
let (pt1, pt2) = thirds(p, pos);
(last, Some(PathCommand::CurveTo(CubicBezierCurve {
pt1,
pt2,
to: pos,
})))
}
}
PathCommand::CurveTo(c) => {
if c.to == pos {
(last, None)
} else {
let prev = sec.unwrap().point();
let bis = bisector(prev, c.to, pos);
let fixed_prev_handle = bis * (c.to - prev).magnitude()/-3.0 + c.to;
let new_pt1 = bis * (pos - c.to).magnitude()/3.0 + c.to;
let new_pt2 = (pos - c.to) * 2.0/3.0 + c.to;
if fixed_prev_handle.is_nan() || new_pt1.is_nan() || new_pt2.is_nan() {
(last, None)
} else {
(PathCommand::CurveTo(CubicBezierCurve {
pt2: fixed_prev_handle,
..c
}), Some(PathCommand::CurveTo(CubicBezierCurve {
pt1: new_pt1,
pt2: new_pt2,
to: pos,
})))
}
}
}
}
}
fn add_point(commands: &mut Vec<PathCommand>, pos: Point) {
let sub = commands
.len()
.checked_sub(2)
.map(|index| commands.get(index))
.flatten()
.copied();
let last = commands.pop().unwrap();
let (last, new) = add_smooth(sub, last, pos);
commands.push(last);
if let Some(new) = new {
commands.push(new);
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct CubicBezierCurve {
pub pt1: Point,
pub pt2: Point,
pub to: Point,
}
impl CubicBezierCurve {
pub fn is_nan(&self) -> bool {
self.pt1.is_nan() || self.pt2.is_nan() || self.to.is_nan()
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Sweep {
Negative,
Positive,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum PathCommand {
MoveTo(Point),
LineTo(Point),
CurveTo(CubicBezierCurve),
}
impl PathCommand {
pub fn is_nan(&self) -> bool {
match self {
PathCommand::MoveTo(p) => p.is_nan(),
PathCommand::LineTo(p) => p.is_nan(),
PathCommand::CurveTo(c) => c.is_nan(),
}
}
fn min_x(&self) -> f64 {
match self {
PathCommand::MoveTo(p) => p.x,
PathCommand::LineTo(p) => p.x,
PathCommand::CurveTo(c) => c.pt1.x.min(c.pt2.x).min(c.to.x),
}
}
fn min_y(&self) -> f64 {
match self {
PathCommand::MoveTo(p) => p.y,
PathCommand::LineTo(p) => p.y,
PathCommand::CurveTo(c) => c.pt1.y.min(c.pt2.y).min(c.to.y),
}
}
fn max_x(&self) -> f64 {
match self {
PathCommand::MoveTo(p) => p.x,
PathCommand::LineTo(p) => p.x,
PathCommand::CurveTo(c) => c.pt1.x.max(c.pt2.x).max(c.to.x),
}
}
fn max_y(&self) -> f64 {
match self {
PathCommand::MoveTo(p) => p.y,
PathCommand::LineTo(p) => p.y,
PathCommand::CurveTo(c) => c.pt1.y.max(c.pt2.y).max(c.to.y),
}
}
fn distance(&self, p: Point) -> f64 {
self.point().distance(p)
}
pub fn point(&self) -> Point {
match *self {
PathCommand::MoveTo(p) => p,
PathCommand::LineTo(p) => p,
PathCommand::CurveTo(c) => c.to,
}
}
pub fn move_to(&self) -> Option<Point> {
match self {
PathCommand::MoveTo(p) => Some(*p),
PathCommand::LineTo(p) => Some(*p),
PathCommand::CurveTo(_) => None,
}
}
pub fn curve_to(&self) -> Option<CubicBezierCurve> {
match self {
PathCommand::MoveTo(_) => None,
PathCommand::LineTo(_) => None,
PathCommand::CurveTo(c) => Some(*c),
}
}
}
impl fmt::Display for PathCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathCommand::MoveTo(p) => write!(f, "M {} ", p),
PathCommand::LineTo(p) => write!(f, "L {} ", p),
PathCommand::CurveTo(c) => write!(f, "C {}, {}, {} ", c.pt1, c.pt2, c.to),
}
}
}
#[derive(Debug, PartialEq)]
pub struct Path {
commands: Vec<PathCommand>,
thickness: f64,
color: Color,
}
impl Path {
pub fn new(color: Color, initial: Point, thickness: f64) -> Path {
let mut commands = Vec::with_capacity(1000);
commands.push(PathCommand::MoveTo(initial));
Path {
commands,
thickness,
color,
}
}
pub fn with_params(color: Color, commands: Vec<PathCommand>, thickness: f64) -> Path {
Path {
commands, color, thickness,
}
}
}
impl ShapeTrait for Path {
fn handle_mouse_moved(&mut self, pos: Point) {
add_point(&mut self.commands, pos);
}
fn handle_button_pressed(&mut self, _pos: Point) { }
fn handle_button_released(&mut self, _pos: Point) -> ShapeFinished {
ShapeFinished::Yes
}
fn draw_commands(&self) -> DrawCommand {
DrawCommand::Path {
commands: self.commands.clone(),
thickness: self.thickness,
color: self.color,
}
}
fn bbox(&self) -> [[f64; 2]; 2] {
let bbox = self.commands.iter().fold([
[std::f64::INFINITY, std::f64::INFINITY],
[std::f64::NEG_INFINITY, std::f64::NEG_INFINITY],
], |acc, cmd| {
[
[
cmd.min_x().min(acc[0][0]),
cmd.min_y().min(acc[0][1]),
],
[
cmd.max_x().max(acc[1][0]),
cmd.max_y().max(acc[1][1]),
],
]
});
assert_ne!(bbox[0][0], std::f64::INFINITY);
assert_ne!(bbox[0][1], std::f64::INFINITY);
assert_ne!(bbox[1][0], std::f64::NEG_INFINITY);
assert_ne!(bbox[1][1], std::f64::NEG_INFINITY);
bbox
}
fn shape_type(&self) -> ShapeType {
ShapeType::Path
}
fn intersects_circle(&self, center: Point, radius: f64) -> bool {
for c in self.commands.iter() {
if c.distance(center) <= radius {
return true;
}
}
false
}
fn color(&self) -> Color {
self.color
}
}
#[cfg(test)]
mod tests {
use super::{Path, thirds, bisector};
use crate::shape::ShapeTrait;
use crate::color::Color;
use crate::point::Point;
#[test]
fn test_thirds() {
assert_eq!(thirds(Point::new(3.0, 0.0), Point::new(6.0, 0.0)), (Point::new(4.0, 0.0), Point::new(5.0, 0.0)));
assert_eq!(thirds(Point::new(0.0, 0.0), Point::new(6.0, 6.0)), (Point::new(2.0, 2.0), Point::new(4.0, 4.0)));
assert_eq!(thirds(Point::new(-15.0, 30.0), Point::new(-18.0, 27.0)), (Point::new(-16.0, 29.0), Point::new(-17.0, 28.0)));
}
#[test]
fn test_bisector() {
let a = (-2.0, 3.0).into();
let b = (1.0, 6.0).into();
let c = (5.0, 2.0).into();
let expected_bisector = Point::new(1.0, 0.0);
let given_bisector = bisector(a, b, c);
assert!(expected_bisector.x - given_bisector.x.abs() < 0.001);
assert!(expected_bisector.y - given_bisector.y.abs() < 0.001);
}
#[test]
fn test_bbox() {
let mut line = Path::new(Color::green(), Point::new(1.0, 0.0), 4.0);
line.handle_mouse_moved(Point::new(0.0, 1.0));
assert_eq!(line.bbox(), [[0.0, 0.0], [1.0, 1.0]]);
}
#[test]
fn test_bbox_twisted_line() {
let mut line = Path::new(Color::green(), Point::new(-12.0, -1.0), 1.0);
line.handle_mouse_moved(Point::new(-5.0, 0.0));
line.handle_mouse_moved(Point::new(-2.0, 7.0));
line.handle_mouse_moved(Point::new(2.0, -8.0));
assert_eq!(line.bbox(), [[-12.0, -8.0], [3.161263886380917, 7.182987048373279]]);
}
}