Skip to content

Commit

Permalink
Added docs to everything
Browse files Browse the repository at this point in the history
  • Loading branch information
gammelalf committed Sep 7, 2022
1 parent de2f568 commit fd58167
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 165 deletions.
5 changes: 4 additions & 1 deletion examples/svg.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use nbezier::bezier::BezierCurve;
use nbezier::svg::SVG;
use nalgebra::Vector2;
use smallvec::smallvec;

#[path = "../src/svg.rs"]
mod svg;
use svg::SVG;

fn main() {
let mut svg = SVG {
view_box: (0.0, 0.0, 100.0, 100.0),
Expand Down
48 changes: 34 additions & 14 deletions src/bezier.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Actual implementation of bezier curves
use crate::bounding_box::BoundingBox;
use crate::graham_scan::convex_hull;
use crate::npolynomial::{Polynomial, Polynomial1xX, Polynomial2xX};
Expand All @@ -9,6 +11,10 @@ use num::One;
use smallvec::{smallvec, SmallVec};
use std::ops::{Add, Deref, DerefMut};

/// A bezier curve is defined by a list of control points.
///
/// Using [`SmallVec`] this struct doesn't need heap allocations for 4 or less points.
/// This is equivalent to a cubic bezier curve, the most common one.
#[derive(Clone, Debug, PartialEq)]
pub struct BezierCurve<T: Scalar>(pub CurveInternal<T>);
type CurveInternal<T> = SmallVec<[Vector2<T>; 4]>;
Expand Down Expand Up @@ -69,7 +75,7 @@ impl<T: ComplexField + Scalar + std::fmt::Display> BezierCurve<T> {
BezierCurve(points)
}

/// Constructs the matrix to raise a curve's degree from n to n+1
/// Constructs the matrix to raise a curve's degree from `n` to `n+1`
fn elevation_matrix(n: usize) -> OMatrix<T, Dynamic, Dynamic> {
let n_plus_one = convert::usize_to_generic::<T>(n + 1);
let mut matrix = OMatrix::zeros_generic(Dynamic::new(n + 1), Dynamic::new(n));
Expand All @@ -91,7 +97,10 @@ impl<T: RealField + Scalar> BezierCurve<T> {
///
/// This box will also contain the whole curve, but can highly overestimate it.
/// It can be used as a fast way to estimate intersections.
/// For more precise checks consider: `minimal_bounding_box` or `convex_hull`
/// For more precise checks consider: [`minimal_bounding_box`] or [`convex_hull`]
///
/// [`minimal_bounding_box`]: BezierCurve::minimal_bounding_box
/// [`convex_hull`]: BezierCurve::convex_hull
pub fn bounding_box(&self) -> BoundingBox<T> {
BoundingBox::from_slice(&self)
}
Expand Down Expand Up @@ -254,9 +263,9 @@ impl<T: RealField + Scalar> BezierCurve<T> {
impl<T: Field + Scalar> BezierCurve<T> {
/// Splits a curve into two parts
///
/// The first part is the same shape as the original curve between 0 and t and the second
/// part as the curve between t and 1.
/// This method assumes `t` to between 0 and 1 but doesn't check it.
/// The first part is the same shape as the original curve between `0` and `t` and the second
/// part as the curve between `t` and `1`.
/// This method assumes `t` to between `0` and `1` but doesn't check it.
pub fn split(&self, t: T) -> (BezierCurve<T>, BezierCurve<T>) {
let inv_t = T::one() - t.clone();
match &self[..] {
Expand Down Expand Up @@ -313,7 +322,9 @@ impl<T: Field + Scalar> BezierCurve<T> {
/// Get the point on the curve at position `t`.
///
/// This method uses de castlejau's algorithm. An alternative way would be to evaluate the
/// curve's polynomial (See `BezierCurve::polynomial`).
/// curve's [`polynomial`].
///
/// [`polynomial`]: BezierCurve::polynomial
pub fn castlejau_eval(&self, t: T) -> Vector2<T> {
let inv_t = T::one() - t.clone();
match &self[..] {
Expand Down Expand Up @@ -364,7 +375,11 @@ impl<T: Field + Scalar> BezierCurve<T> {
impl<T: Field + Scalar> BezierCurve<T> {
/// Computes the curve's polynomial
///
/// This polynomial evaluated between 0 and 1 yields the same points as its corrisponding bezier curve.
/// This polynomial evaluated between `0` and `1` yields the same points as its corrisponding bezier curve.
///
/// If you are only interested in its derivative, use [`derivative`] to get it directly.
///
/// [`derivative`]: BezierCurve::derivative
pub fn polynomial(&self) -> Polynomial2xX<T> {
let zero = T::zero();
let one = T::one();
Expand Down Expand Up @@ -428,8 +443,8 @@ impl<T: Field + Scalar> BezierCurve<T> {

/// Computes the curve's polynomial's derivative
///
/// This method is a faster alternative to calling `Polynomial::derive` on the result of
/// `BezierCurve::polynomial`.
/// This method is a faster alternative to calling [`Polynomial::derive`] on the result of
/// [`BezierCurve::polynomial`].
pub fn derivative(&self) -> Polynomial2xX<T> {
let zero = T::zero();
let one = T::one();
Expand Down Expand Up @@ -479,21 +494,25 @@ impl<T: Field + Scalar> BezierCurve<T> {
/// Computes the curve's tangent vector at `t`
///
/// If you need a lot of them at once, it is far more efficent to call
/// `BezierCurve::derivative` once yourself and evaluate it multiple times,
/// [`derivative`] once yourself and evaluate it multiple times,
/// as this function would recompute the derivative for every vector.
///
/// *The resulting vector is not normalized!*
///
/// [`derivative`]: BezierCurve::derivative
pub fn tangent(&self, t: T) -> Vector2<T> {
self.derivative().evaluate(t.clone())
}

/// Computes the curve's normal vector at `t`
///
/// Similarly to `BezierCurve::tangent` calling this multiple times to get a lot of vectors
/// would be inefficent. Compute their tangent vectors (see `BezierCurve::tangent`) and rotate
/// Similarly to [`tangent`] calling this multiple times to get a lot of vectors
/// would be inefficent. Compute their tangent vectors (see [`tangent`]) and rotate
/// them yourself instead.
///
/// *The resulting vector is not normalized!*
///
/// [`tangent`]: BezierCurve::tangent
pub fn normal(&self, t: T) -> Vector2<T> {
let [[x, y]] = self.tangent(t).data.0;
Vector2::new(T::zero() - y, x)
Expand Down Expand Up @@ -578,7 +597,7 @@ where
/// 3 | 1 2 1
/// 4 | 1 3 3 1
///
/// This function is used in `bernstein_polynomials` to compute all binomial coefficient for a
/// This function is used in [`bernstein_polynomials`] to compute all binomial coefficient for a
/// single `n`.
pub fn pascal_triangle<N>(layer: usize) -> Vec<N>
where
Expand Down Expand Up @@ -613,8 +632,9 @@ where
/// this version can't be generic in its numeric type.
///
/// It isn't used anywhere. It was just a fun exercise.
#[allow(dead_code)]
pub const fn const_pascal_triangle<const L: usize>() -> [usize; L] {
let mut old_layer = [1; L];
let mut old_layer;
let mut new_layer = [1; L];

let mut i = 0;
Expand Down
30 changes: 16 additions & 14 deletions src/bounding_box.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
//! A simple geometry primitive
use nalgebra::Vector2;

/// An axis aligned bounding box
///
/// Used as geometry primitive in calculating curves' intersections.
pub struct BoundingBox<T: PartialOrd> {
/// Corner whose coordinates have the lowest values
pub min: Vector2<T>,

/// Corner whose coordinates have the highest values
pub max: Vector2<T>,
}

impl<T: Clone + PartialOrd> BoundingBox<T> {
/// Create the smallest bounding box containing all points in an iterator.
pub fn from_iter<Iter: Iterator<Item = Vector2<T>>>(mut points: Iter) -> BoundingBox<T> {
let mut min = points.next().expect("Should at least contain two point");
let mut max = min.clone();
Expand All @@ -26,38 +35,31 @@ impl<T: Clone + PartialOrd> BoundingBox<T> {
BoundingBox { min, max }
}

/// Create the smallest bounding box containing all points in an slice.
pub fn from_slice(points: &[Vector2<T>]) -> BoundingBox<T> {
BoundingBox::from_iter(points.iter().map(|p| p.clone()))
}

/// Check whether a point is contained by the box.
///
/// If the point lies on the boundary, it is said to be contained.
pub fn contains(&self, point: Vector2<T>) -> bool {
self.min[0] <= point[0]
&& point[0] <= self.max[0]
&& self.min[1] <= point[1]
&& point[1] <= self.max[1]
}

/// Check whether this box intersects with another one.
pub fn intersects(&self, other: &Self) -> bool {
self.intersecting_interval::<0>(other) && self.intersecting_interval::<1>(other)
}

/// Check if two boxes overlap in their projection on the x or y axis.
fn intersecting_interval<const I: usize>(&self, other: &Self) -> bool {
(self.min[I] < other.min[I] && other.min[I] < self.max[I])
|| (self.min[I] < other.max[I] && other.max[I] < self.max[I])
|| (other.min[I] < self.min[I] && self.min[I] < other.max[I])
|| (other.min[I] < self.max[I] && self.max[I] < other.max[I])
}
}

impl<T: PartialOrd> From<[Vector2<T>; 2]> for BoundingBox<T> {
fn from(array: [Vector2<T>; 2]) -> Self {
let [min, max] = array;
BoundingBox { min, max }
}
}

impl<T: PartialOrd> From<BoundingBox<T>> for [Vector2<T>; 2] {
fn from(bb: BoundingBox<T>) -> Self {
[bb.min, bb.max]
}
}
}
15 changes: 14 additions & 1 deletion src/graham_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@ use nalgebra::{RealField, Vector2, Vector3};
use std::cmp::Ordering;

/// Different types of turns
///
/// This enum is returned by [`turn_type`] as a more readable duplicate of [`Ordering`].
pub enum Turn {
/// A left turn
///
/// i.e. cross product > 0
Left,

/// No turn
///
/// i.e. cross product = 0
None,

/// A right turn
///
/// i.e. cross product < 0
Right,
}

/// Identifies the type of turn 3 points form.
/// Identifies the turn 3 points form by computing the cross product of their differences.
pub fn turn_type<T: RealField>(x: &Vector2<T>, y: &Vector2<T>, z: &Vector2<T>) -> Turn {
// Compute third component of 3d cross product between xy and xz
let x = Vector3::new(x[0].clone(), x[1].clone(), T::zero());
Expand Down
27 changes: 18 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
pub mod bezier;
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]

pub mod bounding_box;
pub mod graham_scan;
pub mod bezier;
pub mod npolynomial;

pub use bezier::BezierCurve;

#[cfg(test)]
mod tests {
use crate::bezier::{bernstein_polynomials, pascal_triangle, BezierCurve};
use crate::bezier::{bernstein_polynomials, pascal_triangle, const_pascal_triangle, BezierCurve};
use nalgebra::{RowDVector, RowVector2, RowVector3, Vector2};
use smallvec::smallvec;
//use crate::graham_scan;
Expand All @@ -16,7 +19,7 @@ mod tests {
#[test]
fn bezier_split() {
let line = BezierCurve(smallvec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0)]);
let (l, u) = line.split(0.5).unwrap();
let (l, u) = line.split(0.5);
assert_eq!(
l,
BezierCurve(smallvec![Vector2::new(0.0, 0.0), Vector2::new(0.5, 0.5)])
Expand Down Expand Up @@ -116,12 +119,18 @@ mod tests {

#[test]
fn pascal() {
assert_eq!(pascal_triangle::<i32>(0), vec![1]);
assert_eq!(pascal_triangle::<i32>(1), vec![1, 1]);
assert_eq!(pascal_triangle::<i32>(2), vec![1, 2, 1]);
assert_eq!(pascal_triangle::<i32>(3), vec![1, 3, 3, 1]);
assert_eq!(pascal_triangle::<i32>(4), vec![1, 4, 6, 4, 1]);
assert_eq!(pascal_triangle::<i32>(5), vec![1, 5, 10, 10, 5, 1]);
assert_eq!(pascal_triangle::<i32>(1), vec![1]);
assert_eq!(pascal_triangle::<i32>(2), vec![1, 1]);
assert_eq!(pascal_triangle::<i32>(3), vec![1, 2, 1]);
assert_eq!(pascal_triangle::<i32>(4), vec![1, 3, 3, 1]);
assert_eq!(pascal_triangle::<i32>(5), vec![1, 4, 6, 4, 1]);
assert_eq!(pascal_triangle::<i32>(6), vec![1, 5, 10, 10, 5, 1]);
assert_eq!(const_pascal_triangle::<1>(), [1]);
assert_eq!(const_pascal_triangle::<2>(), [1, 1]);
assert_eq!(const_pascal_triangle::<3>(), [1, 2, 1]);
assert_eq!(const_pascal_triangle::<4>(), [1, 3, 3, 1]);
assert_eq!(const_pascal_triangle::<5>(), [1, 4, 6, 4, 1]);
assert_eq!(const_pascal_triangle::<6>(), [1, 5, 10, 10, 5, 1]);
}

#[test]
Expand Down
Loading

0 comments on commit fd58167

Please sign in to comment.