%matplotlib inline

import ipywidgets
from ipywidgets import interact

"""
Explanation: Courbes de Bézier
TODO
* https://fr.wikipedia.org/wiki/Courbe_de_B%C3%A9zier#Courbes_de_B.C3.A9zier_cubiques
* https://gist.github.com/Juanlu001/7284462
* https://www.johndcook.com/blog/2009/12/21/bezier-basics/
* https://stackoverflow.com/questions/12643079/b%C3%A9zier-curve-fitting-with-scipy
* Definition using the https://en.wikipedia.org/wiki/Bernstein_polynomial
End of explanation
"""


import numpy as np

p1 = np.array([0, 0])
p2 = np.array([0, 1])
p3 = np.array([1, 1])
p4 = np.array([1, 0])

def f(t):
    return p1 * (1.-t)**3 + 3. * p2 * t * (1.-t)**2 + 3. * p3 * t**2 * (1.-t) + p4 * t**3
            
plt.plot(*p1, "xr")
plt.plot(*p2, "xr")
plt.plot(*p3, "xr")
plt.plot(*p4, "xr")

offset = np.array([0.02, 0.02])

plt.text(*(p1 + offset), r"$P_1$")
plt.text(*(p2 + offset), r"$P_2$")
plt.text(*(p3 + offset), r"$P_3$")
plt.text(*(p4 + offset), r"$P_4$")

XY = np.array([f(t) for t in np.linspace(0., 1., 100)])    # TODO: improve this

plt.plot(*XY.T)

plt.xlim(-0.1, 1.1)
plt.ylim(-0.1, 1.1)

plt.show()

"""
Explanation: Courbe de Bézier quadratique (degré 2)
Écriture paramétrique
La forme paramétrique d'une courbe de Bézier quadratique s'écrit comme une combinaison affine de ses 3 points de contrôle $\mathbf{P}_1$, $\mathbf{P}_2$ et $\mathbf{P}_3 \in \mathbb{R}^2$:
$$
\begin{align}
f: & \mathbb{R} \to \mathbb{R}^2 \
   & \mathbf{f}(t) \mapsto
       \mathbf{P_1} (1-t)^2
       + 2\mathbf{P_2} t (1-t)
       + \mathbf{P_3} t^2
\end{align}
$$
pour $0 \leq t \leq 1$.
TODO...
Courbe de Bézier cubique (degré 3)
Écriture paramétrique
La forme paramétrique d'une courbe de Bézier cubique s'écrit comme une combinaison affine de ses 4 points de contrôle $\mathbf{P}_1$, $\mathbf{P}_2$, $\mathbf{P}_3$ et $\mathbf{P}_4 \in \mathbb{R}^2$:
$$
\begin{align}
f: & \mathbb{R} \to \mathbb{R}^2 \
   & \mathbf{f}(t) \mapsto \mathbf{P}_1 (1-t)^3 + 3 \mathbf{P}_2 t(1-t)^2 + 3 \mathbf{P}_3 t^2 (1-t) + \mathbf{P}_4 t^3
\end{align}
$$
pour $0 \leq t \leq 1$.
End of explanation
"""


# TODO

"""
Explanation: Écriture récursive
Avantage: plus efficace en terme de calculs
TODO:
* https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Recursive_definition
* https://fr.wikipedia.org/wiki/Courbe_de_B%C3%A9zier#Courbes_de_B.C3.A9zier_cubiques
End of explanation
"""


class CubicBezierCurve:
    def __init__(self, p1, p2, p3, p4):
        self.p1 = p1
        self.p2 = p2
        self.p3 = p3
        self.p4 = p4
        
        self.color_deg1 = 'gray'
        self.color_deg2 = 'g'
        self.color_deg3 = 'b'
    
    def __call__(self, t):
        return self.p1 * (1.-t)**3 \
               + 3. * self.p2 * t * (1.-t)**2 \
               + 3. * self.p3 * t**2 * (1.-t) \
               + self.p4 * t**3
                
    def init_plot(self, ax=None):
        if ax is None:
            fig, ax = plt.subplots()
        
        ax.plot(*np.array([self.p1, self.p2]).T, marker='x', color=self.color_deg1)
        ax.plot(*np.array([self.p2, self.p3]).T, marker='x', color=self.color_deg1)
        ax.plot(*np.array([self.p3, self.p4]).T, marker='x', color=self.color_deg1)

        offset = np.array([0.02, 0.02])

        ax.text(*(self.p1 + offset), r"$P_1$")
        ax.text(*(self.p2 + offset), r"$P_2$")
        ax.text(*(self.p3 + offset), r"$P_3$")
        ax.text(*(self.p4 + offset), r"$P_4$")

        T = np.linspace(0., 1., 100)
        XY = np.array([self(t) for t in T])    # TODO: improve this

        ax.plot(*XY.T, 'r:', alpha=0.5)

        points = np.array([self.p1, self.p2, self.p3, self.p4])
        ax.set_xlim(points[:,0].min() - 0.1, points[:,0].max() + 0.1)
        ax.set_ylim(points[:,1].min() - 0.1, points[:,1].max() + 0.1)
        
        return ax
    
    def plot(self, t, ax=None):
        if ax is None:
            fig, ax = plt.subplots()
            
        p12 = self.p1 + t * (self.p2 - self.p1)
        p23 = self.p2 + t * (self.p3 - self.p2)
        p34 = self.p3 + t * (self.p4 - self.p3)
        
        p1223 = p12 + t * (p23 - p12)
        p2334 = p23 + t * (p34 - p23)
        
        ax.plot(*np.array([p12, p23, p34]).T, marker='.', color=self.color_deg2)
        ax.plot(*np.array([p1223, p2334]).T, marker='.', color=self.color_deg3)
        
        ax.plot(*self(t).T, 'or')

p1 = np.array([0, 0])
p2 = np.array([0, 1])
p3 = np.array([1, 1])
p4 = np.array([1, 0])
f = CubicBezierCurve(p1, p2, p3, p4)

ax = f.init_plot()
f.plot(0.25, ax)

plt.show();

"""
Explanation: Exemple illustré
End of explanation
"""


@interact(t=(0., 1., 0.01))
def square(t):
    p1 = np.array([0, 0])
    p2 = np.array([0, 1])
    p3 = np.array([1, 1])
    p4 = np.array([1, 0])
    f = CubicBezierCurve(p1, p2, p3, p4)

    ax = f.init_plot()
    f.plot(t, ax)

    plt.show();

"""
Explanation: Exemple interactif
End of explanation
"""


