Curves.Transform (curves v1.0.1)

Copy Markdown View Source

Utility functions that allow you to move, resize, stretch, flip, rotate and reverse a curve, without changing the points you defined it with.

To...Use
move itset_position/2, inc_position/2, inc_x/2, inc_y/2
resize or stretch itset_scale/3, inc_scale/3 (a number, or {x, y} for each direction)
mirror itflip_x/2, flip_y/2
rotate itset_rotation/3, inc_rotation/3 (degrees, counter-clockwise)
travel it the other wayreverse/1

The curve is first scaled and then rotated around its own {0, 0}, and finally moved to its position (the :origin option, set_position/2 and the like). This is the usual scale-rotate-translate order (like SVG's transform="translate(…) rotate(…) scale(…)"), so scaling or rotating a curve never moves it away from its position. To scale, flip or rotate around some other point, such as the curve's middle, pass around: :center or around: {x, y}.

Positions are {x, y} tuples of numbers, and amounts are numbers. Anything else raises an ArgumentError.

Drawings

Every function here also takes a Curves.Drawing (from Curves.from_svg/2), or a group or shape in one, and changes it as a whole, the same way it changes a curve. Everything in it moves together, text and other kept markup too. around: :center is the middle of its shapes: text and other markup can't be measured, so they don't count. Curves.Drawing.update/3 and update_in/3 change one part, found by its id:

drawing
|> Curves.Transform.set_scale(2)
|> Curves.Drawing.update("wheel", &Curves.Transform.inc_rotation(&1, 45, around: :center))

The change is kept on the drawing, group or shape (in its :origin, :scale and :rotation), so it can be set again later, like a curve's. Curves.Drawing.shapes/1 and curves/1 give the curves where they end up, and Curves.Svg draws it there. A Curves.Drawing.Subpath changes its curve.

Summary

Types

The point to scale, flip or rotate around: :center (the middle of the curve's points) or an {x, y} point, in the curve's own coordinates. nil (the default) is the curve's own {0, 0}.

Anything these functions change: a curve, or a Curves.Drawing, or a group, shape or subpath in one. A drawing, group or shape changes as a whole (everything in it, text too); a subpath changes its curve.

Functions

Mirror the curve left to right (its x scale turns negative, or back again).

Mirror the curve top to bottom (its y scale turns negative, or back again).

Using the current position increase both the x and y coordinates. It is also possible to decrease by passing a negative number

Increase the rotation (counter-clockwise) by the given amount, in degrees.

Resize the curve, adding to the existing scale. With an {x, y} pair, each direction is added to separately; a single number adds to both. A flipped direction has a negative scale, so adding to it brings it towards 0, making the curve smaller that way.

Using the current position increase the x coordinates. It is also possible to decrease by passing a negative number

Using the current position increase the y coordinates. It is also possible to decrease by passing a negative number

The same curve, travelled the other way: t (or u) = 0.0 is now its old end. Its shape, position and transforms don't change. Handy to play an easing backwards, or follow a path in reverse.

Ignore current position and set a new one.

Rotate the curve counter-clockwise, in degrees, replacing the existing rotation. It turns around its position (the :origin option or set_position/2, {0.0, 0.0} by default).

Resize the curve, replacing the existing scale. The default scale is 1.0: 2.0 doubles the curve and 0.5 halves it. A negative scale flips it.

Types

around()

@type around() :: nil | :center | Curves.Types.point_tuple()

The point to scale, flip or rotate around: :center (the middle of the curve's points) or an {x, y} point, in the curve's own coordinates. nil (the default) is the curve's own {0, 0}.

transformable()

Anything these functions change: a curve, or a Curves.Drawing, or a group, shape or subpath in one. A drawing, group or shape changes as a whole (everything in it, text too); a subpath changes its curve.

Functions

flip_x(curve, opts \\ [])

@spec flip_x(transformable(), opts :: [{:around, around()}]) :: transformable()

Mirror the curve left to right (its x scale turns negative, or back again).

The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a different point instead, which then stays exactly where it is: either :center (the middle of the curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined it with, before any transforms.

around: keeps that point still by moving the curve's position, and the move stays: setting the rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.

Examples

iex> curve = Curves.define_bezier([{1, 0}, {3, 2}])
iex> Curves.solve!(Transform.flip_x(curve), 1.0)
{-3.0, 2.0}
iex> Curves.solve!(Transform.flip_x(curve, around: :center), 1.0)
{1.0, 2.0}

flip_y(curve, opts \\ [])

@spec flip_y(transformable(), opts :: [{:around, around()}]) :: transformable()

Mirror the curve top to bottom (its y scale turns negative, or back again).

The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a different point instead, which then stays exactly where it is: either :center (the middle of the curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined it with, before any transforms.

around: keeps that point still by moving the curve's position, and the move stays: setting the rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.

Examples

iex> curve = Curves.define_bezier([{1, 0}, {3, 2}])
iex> Curves.solve!(Transform.flip_y(curve), 1.0)
{3.0, -2.0}
iex> Curves.solve!(Transform.flip_y(curve, around: :center), 1.0)
{3.0, 0.0}

inc_position(curve, pos)

@spec inc_position(curve :: transformable(), Curves.Types.point_tuple()) ::
  transformable()

Using the current position increase both the x and y coordinates. It is also possible to decrease by passing a negative number

Examples

iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> curve = Transform.inc_position(curve, {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
20.5

inc_rotation(curve, amount, opts \\ [])

@spec inc_rotation(transformable(), number(), opts :: [{:around, around()}]) ::
  transformable()

Increase the rotation (counter-clockwise) by the given amount, in degrees.

The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a different point instead, which then stays exactly where it is: either :center (the middle of the curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined it with, before any transforms.

around: keeps that point still by moving the curve's position, and the move stays: setting the rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.

Examples

iex> curve = Curves.define_bezier(:linear)
iex> curve = Transform.inc_rotation(curve, 45.0)
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> {Float.round(x, 5), Float.round(y, 5)}
{0.0, 0.70711}

inc_scale(curve, amount, opts \\ [])

@spec inc_scale(
  transformable(),
  amount :: number() | {number(), number()},
  opts :: [{:around, around()}]
) :: transformable()

Resize the curve, adding to the existing scale. With an {x, y} pair, each direction is added to separately; a single number adds to both. A flipped direction has a negative scale, so adding to it brings it towards 0, making the curve smaller that way.

The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a different point instead, which then stays exactly where it is: either :center (the middle of the curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined it with, before any transforms.

around: keeps that point still by moving the curve's position, and the move stays: setting the rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.

Examples

iex> curve = Curves.define_bezier(:linear)
iex> curve = Transform.inc_scale(curve, 10.0)
iex> Curves.solve!(curve, 0.5)
{5.5, 5.5}
iex> curve = Transform.inc_scale(curve, {0, -10})
iex> Curves.solve!(curve, 0.5)
{5.5, 0.5}

inc_x(curve, xinc)

@spec inc_x(curve :: transformable(), xinc :: Curves.Types.coord()) :: transformable()

Using the current position increase the x coordinates. It is also possible to decrease by passing a negative number

Examples

iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> curve = Transform.inc_x(curve, 10)
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> x
20.5
iex> y
10.5

inc_y(curve, yinc)

@spec inc_y(curve :: transformable(), yinc :: Curves.Types.coord()) :: transformable()

Using the current position increase the y coordinates. It is also possible to decrease by passing a negative number

Examples

iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> y
10.5
iex> curve = Transform.inc_y(curve, -10)
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> y
0.5

reverse(curve)

@spec reverse(transformable()) :: transformable()

The same curve, travelled the other way: t (or u) = 0.0 is now its old end. Its shape, position and transforms don't change. Handy to play an easing backwards, or follow a path in reverse.

Examples

iex> curve = Curves.define_bezier([{0, 0}, {1, 2}])
iex> Curves.solve!(Transform.reverse(curve), 0.0)
{1.0, 2.0}
iex> spline = Curves.define_catmull_rom([{0, 0}, {1, 1}, {2, 0}])
iex> Curves.solve!(Transform.reverse(spline), 2.0)
{0.0, 0.0}

set_position(curve, origin)

@spec set_position(curve :: transformable(), Curves.Types.point_tuple()) ::
  transformable()

Ignore current position and set a new one.

Examples

iex> curve = Curves.define_bezier(:linear)
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
0.5
iex> curve = Curves.Transform.set_position(curve, {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
10.5

set_rotation(curve, amount, opts \\ [])

@spec set_rotation(transformable(), number(), opts :: [{:around, around()}]) ::
  transformable()

Rotate the curve counter-clockwise, in degrees, replacing the existing rotation. It turns around its position (the :origin option or set_position/2, {0.0, 0.0} by default).

The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a different point instead, which then stays exactly where it is: either :center (the middle of the curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined it with, before any transforms.

around: keeps that point still by moving the curve's position, and the move stays: setting the rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.

Examples

iex> curve = Curves.define_bezier(:linear_up_right)
iex> curve = Transform.set_rotation(curve, 90)
iex> {x, y} = Curves.solve!(curve, 1.0)
iex> {Float.round(x, 5), Float.round(y, 5)}
{-1.0, 1.0}

Turned around its middle, the line stays centred on the same spot:

iex> curve = Curves.define_bezier([{0, 0}, {2, 0}]) |> Transform.set_rotation(90, around: :center)
iex> for t <- [0.0, 1.0], {x, y} = Curves.solve!(curve, t), do: {Float.round(x, 5), Float.round(y, 5)}
[{1.0, -1.0}, {1.0, 1.0}]

set_scale(curve, scale, opts \\ [])

@spec set_scale(
  transformable(),
  scale :: number() | {number(), number()},
  opts :: [{:around, around()}]
) :: transformable()

Resize the curve, replacing the existing scale. The default scale is 1.0: 2.0 doubles the curve and 0.5 halves it. A negative scale flips it.

Give an {x, y} pair to scale the two directions separately, e.g. {2, 0.5} makes it twice as wide and half as tall.

The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a different point instead, which then stays exactly where it is: either :center (the middle of the curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined it with, before any transforms.

around: keeps that point still by moving the curve's position, and the move stays: setting the rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.

Examples

iex> curve = Curves.define_bezier(:linear)
iex> Curves.solve!(curve, 0.5)
{0.5, 0.5}
iex> curve = Transform.set_scale(curve, 10.0)
iex> Curves.solve!(curve, 0.5)
{5.0, 5.0}

Separate x and y scales:

iex> curve = Curves.define_bezier([{0, 0}, {2, 2}]) |> Transform.set_scale({2, 0.5})
iex> Curves.solve!(curve, 1.0)
{4.0, 1.0}

The curve is scaled around its own {0, 0}, so a moved curve stays where it was put:

iex> curve = Curves.define_bezier([{0, 0}, {4, 0}]) |> Transform.set_position({10, 10})
iex> curve = Transform.set_scale(curve, 2)
iex> Curves.solve!(curve, 0.0)
{10.0, 10.0}
iex> Curves.solve!(curve, 1.0)
{18.0, 10.0}

Scaled around its middle instead, it grows equally in every direction:

iex> curve = Curves.define_bezier([{0, 0}, {2, 2}]) |> Transform.set_scale(2, around: :center)
iex> {Curves.solve!(curve, 0.0), Curves.solve!(curve, 1.0)}
{{-1.0, -1.0}, {3.0, 3.0}}