Curves

Introduction

Curves aims to be the best elixir framework for calculating bezier curves and splines.

curve = Curves.define_bezier(:ease_out)
        |> Curves.Transform.set_scale(1.5)
        |> Curves.Transform.inc_position({10, 10})
        |> Curves.Transform.set_rotation(90)

# x and y are floats
{x, y} = Curves.solve!(curve, 0.25)

Installation

Add curves to your dependencies in mix.exs:

def deps do
  [
    {:curves, "~> 1.0.0-rc.1"}
  ]
end

Directory

Hire me

Full stack developer seeking new challenges. You can reach me at hireme@aaronjprice.com

Visual Demo

Ease in-out Bezier

The simplest way to use Curves is with the predefined ones like :ease_in_out

curve = Curves.define_bezier(:ease_in_out)

# Find any point along the curve (t goes from 0.0 to 1.0)...
Curves.solve!(curve, 0.25)

# ...or draw the whole curve as an SVG image
Curves.Svg.save!(curve, "ease_in_out.svg")

Ease In Out

Quadratic Bezier

Here we make an asymmetric quadratic curve

points = [
  {0, 0},
  {20, 50},
  {100, 0}
]
curve = Curves.define_bezier(points)

Quadratic

Custom 4-point Bezier

points = [
  {0, 0},
  {0.8, 0.2},
  {0.3, 2.2},
  {1, 1}
]
curve = Curves.define_bezier(points)

Custom Bezier

Spline examples

The simplest spline is basically just a series of bezier curves connected to each other. Notice we are no longer using define_bezier, but define_bezier_spline. Now it is a list of lists with 1, 2, or 3 tuples in each segment.

# points format for `define_bezier_spline/2`
[{knot_x, knot_y}]
[{knot_x, knot_y}, {cp0_x, cp0_y}]
[{knot_x, knot_y}, {cp0_x, cp0_y}, {cp1_x, cp1_y}]

Bezier Spline

points = [
  # P0
  [{5.0, 10.0},  # Knot
  {10.0, 10.0}], # control point 0

  # P1
  [{10.0, 5.0},  # Knot
    {5.0, 5.0},  # control point 0
    {15.0, 5.0}],# control point 1

  # P2
  [{15.0, 10.0}, # Knot
   {15.0, 6.0},  # control point 0
   {15.0, 14.0}  # control point 1
  ],

  # P3
  [{20.0, 15.0}, # Knot
   {18.0, 15.0}, # control point 0
   {23.0, 15.0}],# control point 1

  # P4
  [{30.0, 10.0}, # Knot
    {32.0, 5.0}] # control point 0
]
curve = Curves.define_bezier_spline(points)

Bezier Spline

Cubic Hermite Spline

A smooth curve through every knot. At each knot you give a tangent: the direction (and speed) the curve leaves it with.

curve = Curves.define_hermite([
  # [knot, tangent]
  [{5, 10}, {5, -5}],
  [{10, 5}, {5, 0}],
  [{15, 10}, {5, 5}],
  [{20, 10}, {5, -3}],
  [{25, 5}, {5, 5}],
  [{30, 15}, {5, 10}]
])

Hermite Spline

Catmull-Rom Spline

Similar to the Hermite spline, but now the derivative comes from the slope between the previous and next point.

curve = Curves.define_catmull_rom([
  {0, 0},
  {1, 0},
  {1, 1},
  {0, 1},
  {0, 2},
  {1, 2},
])

Catmull-Rom Spline

B-Spline

The smoothest of the splines featured here, but with the drawback that the line does not actually pass through the control points.

curve = Curves.define_b_spline([
    {0, -2},
    {3, 4},
    {6, 5},
    {7, -1},
    {10, 3},
    {5, 1},
    {12, 9}
])

B-Spline

Benchmarks

Benchmarks live in bench/ and use Benchee. Run them all with:

mix bench

To check a change for slowdowns, save a run before the change, then run again afterwards. A run without a tag is compared against every saved run.

mix bench baseline           # save this run under the tag "baseline"
mix run bench/solve.exs      # later: run one script and compare it to the saved runs

Saved runs go in bench/results/, which is not committed. Only compare runs from the same machine.