2

I am trying to make a bezier function that takes 4 args:

> import Diagrams.Backend.SVG.CmdLine
> import Diagrams.Prelude
> import Control.Applicative

> bezier4 x1 c1 c2 x2 = bezier3 (c1 ^-^ x1) (c2 ^-^ x1) (x2 ^-^ x1) # translate x1
> lineBtwPoints p1 p2 = fromOffsets [p2 ^-^ p1] # translate p1
> illustrateBézier x1 c1 c2 x2
>     =  endpt  # translate x1
>     <> endpt  # translate x2
>     <> ctrlpt # translate c1
>     <> ctrlpt # translate c2
>     <> l1
>     <> l2
>     <> fromSegments [bezier4 x1 c1 c2 x2]
>   where
>     dashed  = dashingN [0.03,0.03] 0
>     endpt   = circle 0.05 # fc red  # lw none
>     ctrlpt  = circle 0.05 # fc blue # lw none
>     l1      = lineBtwPoints x1 c1 # dashed
>     l2      =  lineBtwPoints x2 c2 # dashed
>
> x1      = r2 (0.3, 0.5) :: R2
> x2      = r2 (3,-1) :: R2         -- endpoint
> [c1,c2] = map r2 [(1,2), (3,0)]   -- control points
> example = illustrateBézier x1 c1 c2 x2

But the result seems not to be what I wanted:

enter image description here

qed
  • 22,298
  • 21
  • 125
  • 196
  • But a `bezier4` curve isn't a translated `bezier3` curve as far as I know... – Willem Van Onsem Mar 28 '15 at 22:13
  • oh, so that's not a good name. the bezier3 function always starts with the origin, i just meant to make a function that can start from an arbitrary point. – qed Mar 28 '15 at 22:15
  • It is a good name: as you can see on the [wiki page](http://en.wikipedia.org/wiki/B%C3%A9zier_curve), you can define a Bezier curve for an arbitrary number of points. – Willem Van Onsem Mar 28 '15 at 22:16
  • The 3 in `bezier3` is not the number of argument but the degree of the curve (its a cubic) so the 4 in your function should indicate your bezier curve is of the fourth degree (need 5 points or 4 vectors). Note that bezier3 doesn't "always start at the origin", it's just that it is a Segment and as such has no location (it's a bit like a vector in that). Segments are useful in a list to create complex curves you then localize with `at` their start point. You can then use `fromLocSegments` to get a Diagram. – Jedai Mar 29 '15 at 12:11

1 Answers1

4

First lets address the name. Typically bezier4 would be the name for a function giving a quartic bezier curve segment. A better name would be fixedBezier3, and a better form would be to take Points rather then vectors for the arguments. Indeed this function exists as FCubic from the FixedSegment data type.

If we look at the type of bezier4 we can see where things go wrong:

bezier4 x1 c1 c2 x2 = bezier3 (c1 ^-^ x1) (c2 ^-^ x1) (x2 ^-^ x1) # translate x1

ghci> :t bezier4
bezier4'
  :: (Data.Basis.HasBasis v,
      Data.MemoTrie.HasTrie (Data.Basis.Basis v)) =>
     v -> v -> v -> v -> Segment Closed v

The important part is that the result is a Segment Closed v. Reading the documentation for Segment:

Segments are translationally invariant, that is, they have no particular "location" and are unaffected by translations. They are, however, affected by other transformations such as rotations and scales.

The translation at the end of bezier4 will not have any effect as the type Segment cannot express values that have a location, it just expresses the "shape" and displacement. We can see this in GHCi:

ghci> bezier4 x1 c1 c2 x2
Cubic (0.7 ^& 1.5) (2.7 ^& (-0.5)) (OffsetClosed (2.7 ^& (-1.5)))
ghci> bezier4' x1 c1 c2 x2 # translate (r2 (1000,1000))
Cubic (0.7 ^& 1.5) (2.7 ^& (-0.5)) (OffsetClosed (2.7 ^& (-1.5)))

One fix would be to make the type of bezier4 result in a Located (Segment Closed v). With this type we can at least express the curve wanted:

bezier4' x1 c1 c2 x2 = bezier3 (c1 ^-^ x1) (c2 ^-^ x1) (x2 ^-^ x1) `at` (0 .+^ x1)

ghci> bezier4' x1 c1 c2 x2
Loc { loc = P (0.3 ^& 0.5)
    , unLoc = Cubic (0.7 ^& 1.5) (2.7 ^& (-0.5)) (OffsetClosed (2.7 ^& (-1.5)))
    }

Notice we get the same segment as before, but now we have a location.

ghci> bezier4' x1 c1 c2 x2 # translate (r2 (1000,1000))
Loc { loc = P (1000.3 ^& 1000.5)
    , unLoc = Cubic (0.7 ^& 1.5) (2.7 ^& (-0.5)) (OffsetClosed (2.7 ^& (-1.5)))
    }

We are a bit stuck at this point though. Located segments are not particularly interesting as typically we want to string together many segments as a Trail. A located list of segments gets us there and we can use fromLocSegments:

fromLocSegments :: TrailLike t => Located [Segment Closed (V t)] -> t

Now we have something that will work (with an additional change at the use site of bezier4):

bezier4 x1 c1 c2 x2 = fromSegments [bezier3 (c1 ^-^ x1) (c2 ^-^ x1) (x2 ^-^ x1)]
                    # translate x1

Note that we cannot string together the output of this function with other segments to make longer trails. Diagrams chooses to use strong types with Segment, Trail, Located, and Path only allow values that precisely match what is expressed in output (the "meaning"). For instance, say we want to write fromFixedSegments:

fromFixedSegments :: TrailLike t => [FizedSegment (V t)] -> t

Each cubic segment would have four points, but the result is going to be a trail having the meaning of "no gaps". To do this we will have to throw away the information of the first or last points of adjacent segments. There is no good choice here!

fryguybob
  • 4,390
  • 2
  • 28
  • 36