Skip to content

Instantly share code, notes, and snippets.

@nickovs
Created December 14, 2018 02:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickovs/63f611fe1835c95a220a362e63a92152 to your computer and use it in GitHub Desktop.
Save nickovs/63f611fe1835c95a220a362e63a92152 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Quadratic to cubic Bezier curves\n",
"\n",
"Find the control points for a cubic Bezer curve that corespond to a give quadratic Bezier curve"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, import sympy and set up in-line printing for equations:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from sympy import *\n",
"init_printing(use_latex=\"mathjax\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define the symbols we will use:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"t, p_0, p_1, p_q, p_c1, p_c2 = symbols(\"t p_0 p_1 p_q p_c1 p_c2\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Bezier curves can be reduced to interpolating between interpolations."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def interpolate(a, b, t):\n",
" return (1-t)*a + t * b"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define a quadratic Bezier curve:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$$t \\left(p_{1} t + p_{q} \\left(- t + 1\\right)\\right) + \\left(- t + 1\\right) \\left(p_{0} \\left(- t + 1\\right) + p_{q} t\\right)$$"
],
"text/plain": [
"t⋅(p₁⋅t + p_q⋅(-t + 1)) + (-t + 1)⋅(p₀⋅(-t + 1) + p_q⋅t)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Q = interpolate(interpolate(p_0, p_q, t), interpolate(p_q, p_1, t), t)\n",
"Q"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define a cubic Bezier curve:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$$t \\left(t \\left(p_{1} t + p_{c2} \\left(- t + 1\\right)\\right) + \\left(- t + 1\\right) \\left(p_{c1} \\left(- t + 1\\right) + p_{c2} t\\right)\\right) + \\left(- t + 1\\right) \\left(t \\left(p_{c1} \\left(- t + 1\\right) + p_{c2} t\\right) + \\left(- t + 1\\right) \\left(p_{0} \\left(- t + 1\\right) + p_{c1} t\\right)\\right)$$"
],
"text/plain": [
"t⋅(t⋅(p₁⋅t + p_c2⋅(-t + 1)) + (-t + 1)⋅(p_c1⋅(-t + 1) + p_c2⋅t)) + (-t + 1)⋅(t\n",
"⋅(p_c1⋅(-t + 1) + p_c2⋅t) + (-t + 1)⋅(p₀⋅(-t + 1) + p_c1⋅t))"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"L1 = interpolate(p_0, p_c1, t)\n",
"L2 = interpolate(p_c1, p_c2, t)\n",
"L3 = interpolate(p_c2, p_1, t)\n",
"Q1 = interpolate(L1, L2, t)\n",
"Q2 = interpolate(L2, L3, t)\n",
"C = interpolate(Q1, Q2, t)\n",
"C"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What we care about is these things being equal:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"bezquad = Eq(Q, C)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We are after two unknowns, so we solve the equations for two different values of $t$."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"solutions = nonlinsolve([bezquad.subs(t, Rational(1,3)),\n",
" bezquad.subs(t, Rational(2,3))],\n",
" p_c1, p_c2)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We are given a set of solutions even though there is only one soltion. Converting to a list is an easy way to get the single results."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$$\\left ( \\frac{p_{0} + 2 p_{q}}{3}, \\quad \\frac{p_{1} + 2 p_{q}}{3}\\right )$$"
],
"text/plain": [
"⎛p₀ + 2⋅p_q p₁ + 2⋅p_q⎞\n",
"⎜──────────, ──────────⎟\n",
"⎝ 3 3 ⎠"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"C1, C2 = list(solutions)[0]\n",
"C1, C2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These are our two control points, which sit two thirds of the way along the two lines between the two endpoints and the middle control point for the quadratic Bezier curve.\n",
"\n",
"To prove that this soltion works everywhere and not just at the two points where we evaluationed, we substitute these control points back into the cubic Bezier equation and look at the difference between that and the quadratic version:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$$0$$"
],
"text/plain": [
"0"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Q_C = simplify(C.subs(p_c1, C1).subs(p_c2, C2))\n",
"simplify(Q - Q_C)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment