Usage
# Create a bezier curve, and (optionally) apply any transformations you want.
curve = Curves.define_bezier(:ease_in)
|> Curves.Transform.set_scale(1.5)
|> Curves.Transform.inc_position({10, 10})
|> Curves.Transform.set_rotation(90)
# Find the {x, y} coordinate at 24% from the start
{x, y} = Curves.solve!(curve, 0.24)For a list of all predefined bezier_types, use Curves.Bezier.Predefined.list/0
Custom curves
You can also create a custom bezier curve by passing in a list of {x, y} tuples. They can be any combination of floats and integers.
curve = Curves.define_bezier([
# {x, y}
{0, 0}, # Knot 0. AKA the start point
{0, 0.5}, # Control Point 0
{0.8, 0.4}, # Control Point 1
{1, 1} # Knot 1. AKA the stop point
])
t = 0.1234
{x, y} = Curves.solve!(curve, t)Precision
Results are 64-bit floats (Elixir's own floats), so they're accurate to about 15 significant digits. The exact last digits are not part of the API: they may change in a minor release, for example when a calculation is sped up, so compare results with a small tolerance.
Tutorial
New to curves? The tutorial has lots of pictures, and each chapter is also an interactive Livebook:
- Your first curve:
t,solve,take, and the ready-made curves. - Bezier curves: how control points shape a curve.
- Splines: polylines and the nine kinds of spline, and when to use each.
- Transforms and drawing: moving, scaling and rotating, and
Curves.Svg.
Not sure which curve to use? Choosing a curve compares them all, with real-world examples of each.
Reading SVGs
from_svg/2 reads the shapes in an SVG into a Curves.Drawing of curves, and from_svg_path/2
reads the path data of a single <path>.
Transform
See the Curves.Transform module for functions that allow you to move, resize, and rotate existing curves.
Options
All define_*, solve*, and take* functions can receive the following opts, except
:origin, which only define_* functions read (solve* and take* ignore it).
Options a function doesn't know are ignored, so a misspelt option has no effect.
key | default | description |
|---|---|---|
:origin | {0, 0} | Provides an offset. every point in the curve will automatically be increased by this {x, y} coordinate |
:force_percent | false | If true, take and solve rescale each coordinate to show where it sits between the lowest and highest point on that axis: the lowest becomes 0.0, the highest becomes 1.0, and halfway between them becomes 0.5. The lowest and highest points are measured after any moves, scaling or rotation, including the :origin option. Curves that bend outside their points (e.g. Catmull-Rom) can return values slightly outside 0..1. If every point has the same value on an axis (a flat curve), that axis can't be rescaled, so it returns 0.5. Rotating a flat curve adds tiny rounding errors, so that axis may no longer count as flat and can return values from 0.0 to 1.0. |
:force_percent can also be set for the whole app, e.g. config :curves, force_percent: true.
Opts passed to a function always win over the app config.
:force_percent is read on every solve and take, so changing it in the app config while running
(e.g. with Application.put_env/3) applies to existing curves straight away.
Summary
Functions
Define a spline struct of type: Curves.Spline.Type.BSpline.
Build a new Curves.Bezier.Curve from a list of {x, y} points, or from the name of a
predefined curve (see Curves.Bezier.Predefined.list/0). Anything else, or a name that
isn't predefined, raises an ArgumentError.
Return a Curves.Spline.Curve struct representing a Curves.Spline.Type.BezierSpline.
Can later be passed into solve/2.
Define a new Curves.Spline.Type.Cardinal spline: a Catmull-Rom spline through every knot, with
a :tension that sets how tightly it bends at each knot.
Define a new Curves.Spline.Type.CatmullRom. Only the joins need to be defined, the control points are calculated automatically.
An empty list raises an ArgumentError.
Define a new Curves.Spline.Type.CentripetalCatmullRom spline: a Catmull-Rom spline through every
knot that takes the distances between knots into account, so it never makes little loops or
sharp points, even when some knots are bunched up.
Define a circle, or an arc of one: define_ellipse/3 with both radii r. It takes the same
options, so :from and :to (in degrees) make an arc, like a canvas's arc().
Define an ellipse, or an arc of one: a Curves.Spline.Type.Ellipse centred on center, with
radii {rx, ry} (numbers that are 0 or more; anything else raises an ArgumentError).
Define a new Curves.Spline.Type.Hermite spline: a smooth curve through every knot,
leaving each one along the tangent you give it.
Define a new Curves.Spline.Type.KochanekBartels (TCB) spline: a Catmull-Rom spline through
every knot, with three dials that change how it passes through each knot.
Define a new Curves.Spline.Type.Monotone spline: a smooth curve through data points that never
overshoots them, the usual way to smooth a line chart.
Define a new Curves.Spline.Type.NaturalCubic spline: the smoothest curve through every knot.
Its bend changes smoothly everywhere, and it straightens out at both ends.
Define a Curves.Polyline.Curve: knots joined by straight lines, like SVG's <polyline>.
Read an SVG document into a Curves.Drawing: every shape in it (<path>, <rect>, <circle>,
<ellipse>, <line>, <polyline> and <polygon>), each as one curve per continuous piece.
Read SVG path data (a <path> element's d attribute) into a list of Curves.Drawing.Subpath,
one per continuous piece, each with a bezier spline curve. Every path command works, absolute and
relative: M L H V C S Q T A Z. Bad path data raises an ArgumentError saying where.
The highest t (bezier) or u (spline) that solve/3 accepts. Every curve starts at 0.0.
Given a curve (Curves.Bezier.Curve, Curves.Spline.Curve or Curves.Polyline.Curve) and t, find the point along it.
The raising version of solve/3: returns the {x, y} point, or raises one of the
Curves.Exceptions errors (e.g. Curves.Exceptions.OutOfBoundT).
Take n samples, evenly spaced along the curve.
The raising version of take/3: returns the list of points, or raises one of the Curves.Exceptions errors.
Functions
@spec define_b_spline(Curves.Types.point_list(), Curves.Types.define_opts()) :: Curves.Spline.Curve.t()
Define a spline struct of type: Curves.Spline.Type.BSpline.
Only the knots need to be defined, the control points are calculated automatically.
Needs at least 4 points; n points give n - 3 segments. Fewer raise an ArgumentError.
Options
See available opts
Examples
iex> curve = Curves.define_b_spline([{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}, {1, 2}])
iex> Curves.max_u(curve)
3.0
iex> {x, y} = Curves.solve!(curve, 0.0)
iex> {Float.round(x, 4), Float.round(y, 4)}
{0.8333, 0.1667}
iex> Curves.define_b_spline([{0, 0}, {1, 0}, {1, 1}])
** (ArgumentError) a B-spline needs at least 4 points, got 3
@spec define_bezier( points :: Curves.Types.point_list() | Curves.Bezier.Predefined.curve_key(), Curves.Types.define_opts() ) :: Curves.Bezier.Curve.t()
Build a new Curves.Bezier.Curve from a list of {x, y} points, or from the name of a
predefined curve (see Curves.Bezier.Predefined.list/0). Anything else, or a name that
isn't predefined, raises an ArgumentError.
Options
See available opts
Examples
iex> c = Curves.define_bezier([{0.1, 0.9}, {0.5, 0.9}, {0.5, 0.1}, {0.75, 0.1}])
iex> is_struct(c, Curves.Bezier.Curve)
true
@spec define_bezier_spline( points :: [Curves.Types.point_list()], opts :: Curves.Types.define_opts() ) :: Curves.Spline.Curve.t()
Return a Curves.Spline.Curve struct representing a Curves.Spline.Type.BezierSpline.
Can later be passed into solve/2.
Options
See available opts
Each point is a knot, then the control points of the curve to the next knot, like an SVG path:
[knot] is a straight line (SVG L), [knot, c] a quadratic curve (Q) and [knot, c0, c1]
a cubic curve (C). The last point only ends the curve, so it must be [knot].
Any other shape, or an empty list, raises an ArgumentError.
[knot, {:arc, rx, ry, rotation, large_arc, sweep}] is an elliptical arc to the next knot, exactly
like SVG's A rx ry rotation large-arc-flag sweep-flag x y, with true/false for the flags and
the rotation in degrees. See Curves.Spline.Type.BezierSpline for what the flags do.
Examples
iex> curve = Curves.define_bezier_spline([
...> [{5.0, 10.0}, {10.0, 10.0}, {5.0, 5.0}],
...> [{10.0, 5.0}, {15.0, 5.0}, {15.0, 6.0}],
...> [{15.0, 10.0}, {15.0, 14.0}, {18.0, 15.0}],
...> [{20.0, 15.0}, {23.0, 15.0}, {32.0, 5.0}],
...> [{30.0, 10.0}]
...> ])
iex> Curves.max_u(curve)
4.0
iex> Curves.solve!(curve, 1.0)
{10.0, 5.0}
iex> Curves.define_bezier_spline([])
** (ArgumentError) a spline needs at least 1 point, got noneA line, then a half circle (SVG M 0 0 L 10 0 A 5 5 0 0 1 10 10):
iex> curve = Curves.define_bezier_spline([[{0, 0}], [{10, 0}, {:arc, 5, 5, 0, false, true}], [{10, 10}]])
iex> Curves.solve!(curve, 1.5)
{15.0, 5.0}
iex> Curves.solve!(curve, 2.0)
{10.0, 10.0}
@spec define_cardinal(Curves.Types.point_list(), Curves.Types.cardinal_opts()) :: Curves.Spline.Curve.t()
Define a new Curves.Spline.Type.Cardinal spline: a Catmull-Rom spline through every knot, with
a :tension that sets how tightly it bends at each knot.
n knots give n - 1 segments. An empty list raises an ArgumentError.
Options
:tension (default 0.0): 0.0 is exactly define_catmull_rom/2. Higher values bend more
tightly at each knot, and 1.0 gives straight lines that slow down at each knot. A tension that
isn't a number raises an ArgumentError.
Also see available opts.
Examples
iex> knots = [{0, 0}, {2, 3}, {4, 1}, {6, 4}, {8, 2}]
iex> curve = Curves.define_cardinal(knots, tension: 0.5)
iex> Curves.max_u(curve)
4.0
iex> Curves.solve!(curve, 1.0)
{2.0, 3.0}
iex> Curves.solve!(curve, 0.5)
{1.0, 1.65625}
iex> Curves.solve!(Curves.define_cardinal(knots), 0.5) == Curves.solve!(Curves.define_catmull_rom(knots), 0.5)
true
iex> Curves.define_cardinal(knots, tension: :high)
** (ArgumentError) the :tension option must be a number, got: :high
@spec define_catmull_rom(Curves.Types.point_list(), Curves.Types.define_opts()) :: Curves.Spline.Curve.t()
Define a new Curves.Spline.Type.CatmullRom. Only the joins need to be defined, the control points are calculated automatically.
An empty list raises an ArgumentError.
Options
See available opts
Examples
iex> curve = Curves.define_catmull_rom([{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}, {1, 2}])
iex> Curves.solve!(curve, 1.0)
{1.0, 0.0}
iex> Curves.solve!(curve, 0.5)
{0.5625, -0.0625}
iex> Curves.define_catmull_rom([])
** (ArgumentError) a spline needs at least 1 point, got none
@spec define_centripetal_catmull_rom( Curves.Types.point_list(), Curves.Types.centripetal_opts() ) :: Curves.Spline.Curve.t()
Define a new Curves.Spline.Type.CentripetalCatmullRom spline: a Catmull-Rom spline through every
knot that takes the distances between knots into account, so it never makes little loops or
sharp points, even when some knots are bunched up.
n knots give n - 1 segments. An empty list raises an ArgumentError.
Options
:alpha (default 0.5), between 0.0 and 1.0: 0.5 is centripetal, 0.0 is exactly
define_catmull_rom/2, and 1.0 is chordal. Anything else raises an ArgumentError.
Also see available opts.
Examples
iex> curve = Curves.define_centripetal_catmull_rom([{0, 0}, {10, 10}, {10.5, 10}, {20, 0}])
iex> Curves.max_u(curve)
3.0
iex> Curves.solve!(curve, 1.0)
{10.0, 10.0}
iex> Curves.define_centripetal_catmull_rom([{0, 0}, {1, 3}], alpha: 2)
** (ArgumentError) the :alpha option must be between 0.0 and 1.0, got: 2.0
@spec define_circle(Curves.Types.point_tuple(), number(), Curves.Types.ellipse_opts()) :: Curves.Spline.Curve.t()
Define a circle, or an arc of one: define_ellipse/3 with both radii r. It takes the same
options, so :from and :to (in degrees) make an arc, like a canvas's arc().
Examples
iex> curve = Curves.define_circle({0, 0}, 2, from: 90, to: 180)
iex> {x, y} = Curves.solve!(curve, 1.0)
iex> {Float.round(x, 6), Float.round(y, 6)}
{-2.0, 0.0}
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> {Float.round(x, 4), Float.round(y, 4)}
{-1.4142, 1.4142}
@spec define_ellipse( Curves.Types.point_tuple(), {number(), number()}, Curves.Types.ellipse_opts() ) :: Curves.Spline.Curve.t()
Define an ellipse, or an arc of one: a Curves.Spline.Type.Ellipse centred on center, with
radii {rx, ry} (numbers that are 0 or more; anything else raises an ArgumentError).
It is one segment, so u goes from 0.0 to 1.0. By default it goes once all the way round,
starting at center + {rx, 0} like SVG's <ellipse>, and ends exactly where it started.
Options
All in degrees:
:rotation(default0) - tilts the ellipse's x axis, turning it around its centre.:fromand:to(defaults0and360) - the angles the curve starts and ends at, measured from the ellipse's x axis, to make an arc. When:tois bigger, the angle goes up: anticlockwise in Curves' pictures (y up), clockwise on an SVG screen (y down). When it is smaller, the other way.
Also see available opts.
Examples
iex> curve = Curves.define_ellipse({1, 2}, {4, 2})
iex> Curves.max_u(curve)
1.0
iex> Curves.solve!(curve, 0.0)
{5.0, 2.0}
iex> Curves.solve!(curve, 1.0)
{5.0, 2.0}
iex> {x, y} = Curves.solve!(curve, 0.25)
iex> {Float.round(x, 6), Float.round(y, 6)}
{1.0, 4.0}
iex> Curves.define_ellipse({0, 0}, {-1, 2})
** (ArgumentError) the radii of an ellipse must be {rx, ry}, two numbers that are 0 or more, got: {-1, 2}
@spec define_hermite([[Curves.Types.point_tuple()]], Curves.Types.define_opts()) :: Curves.Spline.Curve.t()
Define a new Curves.Spline.Type.Hermite spline: a smooth curve through every knot,
leaving each one along the tangent you give it.
Each point is [knot, tangent], both {x, y}. The tangent is the curve's velocity at that knot
per unit of u. Needs at least 2 points; n points give n - 1 segments.
Options
See available opts
Examples
iex> curve = Curves.define_hermite([[{0, 0}, {4, 0}], [{2, 2}, {0, 4}], [{4, 0}, {4, 0}]])
iex> Curves.max_u(curve)
2.0
iex> Curves.solve!(curve, 1.0)
{2.0, 2.0}
iex> Curves.solve!(curve, 0.5)
{1.5, 0.5}
@spec define_kochanek_bartels( Curves.Types.point_list(), Curves.Types.kochanek_bartels_opts() ) :: Curves.Spline.Curve.t()
Define a new Curves.Spline.Type.KochanekBartels (TCB) spline: a Catmull-Rom spline through
every knot, with three dials that change how it passes through each knot.
n knots give n - 1 segments. An empty list raises an ArgumentError.
Options
Each dial is a number, usually between -1.0 and 1.0, and 0.0 by default. With all three at
0.0 it is exactly define_catmull_rom/2. A dial that isn't a number raises an ArgumentError.
:tension- how tightly it bends at each knot, as indefine_cardinal/2.:continuity- below0.0makes sharper corners (-1.0turns like a polyline), above0.0makes corners that bulge out.:bias- below0.0turns early, towards the next knot; above0.0turns late, overshooting.
Also see available opts.
Examples
iex> knots = [{0, 0}, {2, 3}, {4, 1}, {6, 4}, {8, 2}]
iex> curve = Curves.define_kochanek_bartels(knots, continuity: -1.0)
iex> Curves.solve!(curve, 1.0)
{2.0, 3.0}
iex> Curves.solve!(curve, 0.5)
{1.0, 1.5}
iex> Curves.solve!(Curves.define_kochanek_bartels(knots), 0.5) == Curves.solve!(Curves.define_catmull_rom(knots), 0.5)
true
@spec define_monotone(Curves.Types.point_list(), Curves.Types.define_opts()) :: Curves.Spline.Curve.t()
Define a new Curves.Spline.Type.Monotone spline: a smooth curve through data points that never
overshoots them, the usual way to smooth a line chart.
Each knot is a data point, so its x must be bigger than the knot before it (or every x
smaller), otherwise it raises an ArgumentError. Between two knots the curve only goes up, only
goes down, or stays flat. n knots give n - 1 segments, and within each one x moves at a
steady speed.
Options
See available opts
Examples
iex> curve = Curves.define_monotone([{0, 0}, {1, 0}, {2, 3}, {3, 3}, {4, 1}])
iex> Curves.max_u(curve)
4.0
iex> Curves.solve!(curve, 0.5)
{0.5, 0.0}
iex> Curves.solve!(curve, 1.5)
{1.5, 1.5}
iex> Curves.define_monotone([{0, 0}, {2, 1}, {1, 2}])
** (ArgumentError) the knots of a monotone spline must have x always increasing (or always decreasing), so each knot is a data point. Got x values: [0.0, 2.0, 1.0]. See Curves.define_monotone/2.
@spec define_natural_cubic(Curves.Types.point_list(), Curves.Types.define_opts()) :: Curves.Spline.Curve.t()
Define a new Curves.Spline.Type.NaturalCubic spline: the smoothest curve through every knot.
Its bend changes smoothly everywhere, and it straightens out at both ends.
Every knot affects the whole curve, so moving one knot changes every segment a little.
n knots give n - 1 segments. An empty list raises an ArgumentError.
Options
See available opts
Examples
iex> curve = Curves.define_natural_cubic([{0, 0}, {2, 3}, {4, 1}, {6, 4}, {8, 2}])
iex> Curves.max_u(curve)
4.0
iex> Curves.solve!(curve, 1.0)
{2.0, 3.0}
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> {Float.round(x, 4), Float.round(y, 4)}
{1.0, 2.1696}
@spec define_polyline(Curves.Types.point_list(), Curves.Types.define_opts()) :: Curves.Polyline.Curve.t()
Define a Curves.Polyline.Curve: knots joined by straight lines, like SVG's <polyline>.
Like a spline, it is solved with u: each line adds one to it, so u = 1.0 is the second knot and
max_u/1 is the last one. Within a line the speed is steady: u = 0.5 is exactly halfway along the
first line. An empty list raises an ArgumentError.
Options
See available opts
Examples
iex> curve = Curves.define_polyline([{0, 0}, {2, 4}, {6, 4}])
iex> Curves.max_u(curve)
2.0
iex> Curves.solve!(curve, 0.5)
{1.0, 2.0}
iex> Curves.solve!(curve, 1.25)
{3.0, 4.0}
iex> Curves.define_polyline([])
** (ArgumentError) a polyline needs at least 1 point, got none
@spec from_svg( String.t(), keyword() ) :: Curves.Drawing.t()
Read an SVG document into a Curves.Drawing: every shape in it (<path>, <rect>, <circle>,
<ellipse>, <line>, <polyline> and <polygon>), each as one curve per continuous piece.
Paths become bezier splines with exact arcs, rectangles and circles become splines too, and
lines, polylines and polygons become polylines (see Curves.Drawing.Subpath). Every transform,
on a shape or on the groups around it, is applied to the curves exactly, even skews. Anything
that can't be read as curves is listed in the drawing's :skipped field, with the reason.
The drawing keeps the SVG's structure: its groups, its shapes' styles, and everything that isn't
curves (text, <defs>, <style>, ...) as markup. So Curves.Svg.save!/3 draws it again just as
it was, and you can change its curves in between.
The coordinates are SVG's own, where y goes down the screen. Curves' own pictures of curves
(Curves.Svg's :inspect mode) draw y going up, as in maths, so pass flip_y: true to turn the
drawing's y around for those.
Reading is safe on SVGs from anywhere: it has no XML library, never creates atoms from the input,
and reads in one pass. <script> and <foreignObject> elements, animations of links and event
handlers, event attributes (onclick, ...) and javascript: links (even disguised with spaces)
are never kept, so they aren't in a drawing drawn again, and the markup it keeps is always well
formed. That isn't a full
sanitizer, though: don't show untrusted SVGs to people without one. Input that isn't a string, or
has no <svg> element, raises an ArgumentError.
Not read as curves (but kept as markup, so they are still drawn again): text, images, <use>, a
nested <svg>, and shapes with sizes in units other than px. CSS (style attributes and
<style>) isn't applied to the curves, but it is kept with them. An empty group written as
<g/> is left out, since it draws nothing.
Options
:flip_y(defaultfalse) - mirror every y, so y goes up.
Examples
iex> drawing = Curves.from_svg(~s(<svg viewBox="0 0 20 10"><rect x="1" y="2" width="4" height="3" fill="red"/></svg>))
iex> drawing.view_box
{0.0, 0.0, 20.0, 10.0}
iex> [%Curves.Drawing.Shape{element: :rect, subpaths: [subpath]} = rect] = Curves.Drawing.shapes(drawing)
iex> rect.attributes["fill"]
"red"
iex> Curves.solve!(subpath.curve, 1.0)
{5.0, 2.0}Transforms are applied, and what can't be read is listed:
iex> drawing = Curves.from_svg(~s[<svg><g transform="translate(10 0)"><line x2="5"/></g><text>Hi</text></svg>])
iex> [line] = Curves.Drawing.curves(drawing)
iex> Curves.solve!(line, 1.0)
{15.0, 0.0}
iex> drawing.skipped
[%{element: "text", id: nil, reason: :not_curves, kept: :as_markup}]
@spec from_svg_path( String.t(), keyword() ) :: [Curves.Drawing.Subpath.t()]
Read SVG path data (a <path> element's d attribute) into a list of Curves.Drawing.Subpath,
one per continuous piece, each with a bezier spline curve. Every path command works, absolute and
relative: M L H V C S Q T A Z. Bad path data raises an ArgumentError saying where.
Options
:flip_y(defaultfalse) - mirror every y, so y goes up. Seefrom_svg/2.
Examples
iex> [subpath] = Curves.from_svg_path("M 0 0 L 10 0 A 5 5 0 0 1 10 10 Z")
iex> subpath.closed
true
iex> Curves.solve!(subpath.curve, 1.5)
{15.0, 5.0}
iex> Curves.from_svg_path("M 0 0 L 10")
** (ArgumentError) invalid path data: expected a number, got: the end in the L command
@spec max_u( Curves.Bezier.Curve.t() | Curves.Spline.Curve.t() | Curves.Polyline.Curve.t() ) :: float()
The highest t (bezier) or u (spline) that solve/3 accepts. Every curve starts at 0.0.
Beziers always end at 1.0. Splines and polylines end at one unit per segment,
so u = 1.0 is the end of the first segment and u = max_u(curve) is the end of the curve.
Examples
iex> Curves.max_u(Curves.define_bezier(:ease_in))
1.0
iex> Curves.max_u(Curves.define_catmull_rom([{0, 0}, {10, 100}, {20, 0}, {30, 100}]))
3.0
iex> Curves.max_u(Curves.define_polyline([{0, 0}, {1, 1}, {2, 0}]))
2.0
@spec solve( Curves.Bezier.Curve.t() | Curves.Spline.Curve.t() | Curves.Polyline.Curve.t(), Curves.Types.t(), Curves.Types.opts() ) :: {:ok, Curves.Types.point_tuple()} | {:error, term()}
Given a curve (Curves.Bezier.Curve, Curves.Spline.Curve or Curves.Polyline.Curve) and t, find the point along it.
For a bezier curve, t must be between 0.0 and 1.0. A t that isn't a number raises an ArgumentError.
e.g. t = 0.5 means the coordinate at 50% through the bezier curve.
For splines, the range of t depends on the number of segments (number of knots - 1).
e.g. t = 0.5 means the coordinate at 50% through the first segment. But t = 1.5 is 50% through the next segment.
Splines and polylines allow for rounding errors at both ends: a u up to 0.001 past
max_u/1 gives the end of the curve, and a u down to -0.001 gives a point just before
its start.
Examples
iex> c = Curves.define_bezier([{0.1, 0.9}, {0.5, 0.9}, {0.5, 0.1}, {0.75, 0.1}])
iex> {:ok, {x, y}} = Curves.solve(c, 0.3)
iex> {Float.round(x, 3), Float.round(y, 3)}
{0.37, 0.727}
iex> Curves.solve(c, 1.5)
{:error, {:out_of_bounds, 1.5}}Options
See available opts.
@spec solve!( Curves.Bezier.Curve.t() | Curves.Spline.Curve.t() | Curves.Polyline.Curve.t(), Curves.Types.t(), Curves.Types.opts() ) :: Curves.Types.point_tuple()
The raising version of solve/3: returns the {x, y} point, or raises one of the
Curves.Exceptions errors (e.g. Curves.Exceptions.OutOfBoundT).
Examples
iex> Curves.solve!(Curves.define_bezier(:linear), 0.5)
{0.5, 0.5}
@spec take( Curves.Bezier.Curve.t() | Curves.Spline.Curve.t() | Curves.Polyline.Curve.t(), n :: pos_integer(), Curves.Types.opts() ) :: {:ok, Curves.Types.point_list()} | {:error, term()}
Take n samples, evenly spaced along the curve.
The samples are at t = 1/n, 2/n, ... 1.0 for a bezier (or the same steps up to
max_u/1 for a spline). The start, t = 0, is not included: call solve/3 with 0.0 if you need it.
n must be a positive integer, otherwise it raises an ArgumentError.
Examples
iex> Curves.take(Curves.define_bezier(:linear), 4)
{:ok, [{0.25, 0.25}, {0.5, 0.5}, {0.75, 0.75}, {1.0, 1.0}]}
iex> Curves.take(Curves.define_bezier([{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}]), 2)
{:error, {:too_many_points, 5, 4}}
iex> Curves.take(Curves.define_bezier(:linear), 0)
** (ArgumentError) n must be a positive integer (the number of samples), got: 0
@spec take!( Curves.Bezier.Curve.t() | Curves.Spline.Curve.t() | Curves.Polyline.Curve.t(), n :: pos_integer(), Curves.Types.opts() ) :: Curves.Types.point_list()
The raising version of take/3: returns the list of points, or raises one of the Curves.Exceptions errors.
Examples
iex> Curves.take!(Curves.define_bezier([{0, 0}, {1, 2}, {2, 0}]), 2)
[{1.0, 1.0}, {2.0, 0.0}]