1import numpy as np
2
3from ..constants import res_path as res
4from ..constants import tol_path as tol
5
6
7def discretize_bezier(points,
8                      count=None,
9                      scale=1.0):
10    """
11    Parameters
12    ----------
13    points : (order, dimension) float
14      Control points of the bezier curve
15      For a 2D cubic bezier, order=3, dimension=2
16    count : int, or None
17      Number of segments
18    scale : float
19      Scale of curve
20    Returns
21    ----------
22    discrete: (n, dimension) float
23     Points forming a a polyline representation
24    """
25    # make sure we have a numpy array
26    points = np.asanyarray(points, dtype=np.float64)
27
28    if count is None:
29        # how much distance does a small percentage of the curve take
30        # this is so we can figure out how finely we have to sample t
31        norm = np.linalg.norm(np.diff(points, axis=0), axis=1).sum()
32        count = np.ceil(norm / (res.seg_frac * scale))
33        count = int(np.clip(count,
34                            res.min_sections * len(points),
35                            res.max_sections * len(points)))
36    count = int(count)
37
38    # parameterize incrementing 0.0 - 1.0
39    t = np.linspace(0.0, 1.0, count)
40    # decrementing 1.0-0.0
41    t_d = 1.0 - t
42    n = len(points) - 1
43    # binomial coefficients, i, and each point
44    iterable = zip(binomial(n), np.arange(len(points)), points)
45    # run the actual interpolation
46    stacked = [((t**i) * (t_d**(n - i))).reshape((-1, 1))
47               * p * c for c, i, p in iterable]
48    result = np.sum(stacked, axis=0)
49
50    # test to make sure end points are correct
51    test = np.sum((result[[0, -1]] - points[[0, -1]])**2, axis=1)
52    assert (test < tol.merge).all()
53    assert len(result) >= 2
54
55    return result
56
57
58def discretize_bspline(control,
59                       knots,
60                       count=None,
61                       scale=1.0):
62    """
63    Given a B-Splines control points and knot vector, return
64    a sampled version of the curve.
65
66    Parameters
67    ----------
68    control : (o, d) float
69      Control points of the b- spline
70    knots : (j,) float
71      B-spline knots
72    count : int
73      Number of line segments to discretize the spline
74      If not specified will be calculated as something reasonable
75
76    Returns
77    ----------
78    discrete : (count, dimension) float
79       Points on a polyline version of the B-spline
80    """
81
82    # evaluate the b-spline using scipy/fitpack
83    from scipy.interpolate import splev
84    # (n, d) control points where d is the dimension of vertices
85    control = np.asanyarray(control, dtype=np.float64)
86    degree = len(knots) - len(control) - 1
87    if count is None:
88        norm = np.linalg.norm(np.diff(control, axis=0), axis=1).sum()
89        count = int(np.clip(norm / (res.seg_frac * scale),
90                            res.min_sections * len(control),
91                            res.max_sections * len(control)))
92
93    ipl = np.linspace(knots[0], knots[-1], count)
94    discrete = splev(ipl, [knots, control.T, degree])
95    discrete = np.column_stack(discrete)
96
97    return discrete
98
99
100def binomial(n):
101    """
102    Return all binomial coefficients for a given order.
103
104    For n > 5, scipy.special.binom is used, below we hardcode
105    to avoid the scipy.special dependency.
106
107    Parameters
108    --------------
109    n : int
110      Order of binomial
111
112    Returns
113    ---------------
114    binom : (n + 1,) int
115      Binomial coefficients of a given order
116    """
117    if n == 1:
118        return [1, 1]
119    elif n == 2:
120        return [1, 2, 1]
121    elif n == 3:
122        return [1, 3, 3, 1]
123    elif n == 4:
124        return [1, 4, 6, 4, 1]
125    elif n == 5:
126        return [1, 5, 10, 10, 5, 1]
127    else:
128        from scipy.special import binom
129        return binom(n, np.arange(n + 1))
130