Am Di., 16. Apr. 2019 um 23:45 Uhr schrieb Aaron Hill
<lilyp...@hillvisions.com>:
>
> On 2019-04-16 10:37 am, Thomas Morley wrote:
> > Am Mo., 15. Apr. 2019 um 19:26 Uhr schrieb Lukas-Fabian Moser
> > <l...@gmx.de>:
> >>
> >> Folks,
> >>
> >> in
> >> https://archiv.lilypondforum.de/index.php?topic=1744.msg9669#msg9669,
> >> Harm invented a truly wonderful new feature allowing to add an arrow
> >> head to the right end of a Slur (or, for that matter, a Tie,
> >> PhrasingSlur etc.). I reproduce it here with only trivial changes
> >> (mainly omitting parser/location).
> >>
> >> Now I also need slurs with arrows pointing to the left (and ideally,
> >> also the option to have an arrow tip at both ends of the Slur). At
> >> first
> >> glance the asymmetry favoring the right hand side of a Slur seems to
> >> be
> >> hard-coded pretty deeply in Harm's code. Is there a cheap way to add a
> >> choice of "left or right end" (if not even the "or/and" possibility)?
> >>
> >> Best
> >> Lukas
> >
> > Hi Lukas,
> >
> > I started to implement the functionality, finally I more or less
> > rewrote anything.
> > As David K once said: rewriting all means at least knowing where the
> > bugs are...
>
> Harm,
>
> There is an annoying optical issue where using the angle of the curve at
> the end points does not work well for an arrow head that partially
> overlaps the curve.  Instead, one needs to consider the slope of the
> curve a little inwards from the ends so that the arrow appears to be
> aligned properly.
>
> I took a stab at patching your code to address this.  This involved some
> additional computational work for various metrics of a Bezier curve.
> See the attached files.

Hi Aaron,

thanks a lot for this.
I was aware of not going for the bezier-curve itself, but only for the
control-points was a raw approximation.
Yours is far better.
Mostly I did so for reasons of lacking knowledge of beziers, both the
math and how to compute them.
Now there is a fine tool-set available, many thanks again.
I tried to understand what you coded (not finished yet), but while
playing around with code I always think making it visible will help.
Thus the attached file and image.
One question I really couldn't answer is:
What kind of value is `t´ in (define (bezier-angle control-points t) ...)
Seems not to be a x- or y-value, not an arc-length-value .., but what else?

Now all those nice bezier-tools are done I made some performance tests.
Running my arrow-slur-03.ly gives
real 0m4,318s
user 0m4,000s
sys 0m0,272s

Your arrow-slur-03-patch.ly is a little slower
real 0m4,661s
user 0m4,075s
sys 0m0,562s

I posted the best values of multiple runs for both.

I then implemented a counter in your (bezier-curve control-points t).
It's called scaring 78368 times, which may explain it.

> Among the things I changed is that the code that adds the arrows to the
> ends of the curve no longer applies an offset.  This offset was strictly
> horizontal which did not work well for more heavily rotated arrows.
> Instead, the offset is done within the code that computes and rotates
> the arrow, so that the center of rotation is properly defined.  What I
> found is that no special case for ties needed to be applied, as the
> results looked uniform between the different grobs.

Up to now I didn't look at that part of your changes, was playing with
a new set of (bezier-)toys, lol

> Also, I "fixed" the font-size issue by bypassing the font settings
> within the grob itself, because simply scaling the glyph results in
> thicker lines.  So while font-size is now consistent between the
> different grobs, it is unfortunately now a hard-coded value.  I am
> uncertain whether this tradeoff would be acceptable.

Not sure either, why does Tie has a font-size property at all?

Best,
  Harm
\version "2.19.82"

%% Code by Aaron

#(define (bezier-curve control-points t)
"Given a Bezier curve of arbitrary degree specified by @var{control-points},
compute the point at the specified position @var{t}."
  (if (< 1 (length control-points))
      (let ((q0 (bezier-curve (drop-right control-points 1) t))
            (q1 (bezier-curve (drop control-points 1) t)))
        (cons
          (+ (* (car q0) (- 1 t)) (* (car q1) t))
          (+ (* (cdr q0) (- 1 t)) (* (cdr q1) t))))
      (car control-points)))

#(define (bezier-angle control-points t)
"Given a Bezier curve of arbitrary degree specified by @var{control-points},
compute the slope at the specified position @var{t}."
  (let ((q0 (bezier-curve (drop-right control-points 1) t))
        (q1 (bezier-curve (drop control-points 1) t)))
    (ly:angle (- (car q1) (car q0)) (- (cdr q1) (cdr q0)))))
    
#(define (bezier-approx-length control-points from to)
"Given a Bezier curve of arbitrary degree specified by @var{control-points},
compute its approximate arc length between the positions @var{from} and @var{to}."
  (let* ((steps 11) ;; Should be accurate enough for reasonable execution time.
         (params (iota steps from (/ (- to from) (1- steps))))
         (points (map (lambda (x) (bezier-curve control-points x)) params))
         (length 
           (fold 
             (lambda (a b prev) 
               (+ prev (ly:length (- (car a) (car b)) (- (cdr a) (cdr b)))))
             0 
             (drop points 1) 
             (drop-right points 1))))
    ; Need to support negative length when the range is inverted.
    (if (< from to) length (- length))))

#(define (bezier-approx-position control-points length)
"Given a Bezier curve of arbitrary degree specified by @var{control-points},
compute the approximate position that is @var{length} distance along the curve."

  (define (helper control-points target-length precision end)
    (let ((actual-length (bezier-approx-length control-points 0 end)))
      (if (> precision (abs (- actual-length target-length)))
          end
          (helper control-points target-length precision
            (* end (/ target-length actual-length))))))
          
  (helper control-points length 0.01 0.5))
  
%% Code by Harm

#(define (bezier-find-coords-for-angle control-points t)
  (let ((q0 (bezier-curve (drop-right control-points 1) t))
        (q1 (bezier-curve (drop control-points 1) t)))
    (list q0 q1)))

#(define (make-bezier-stencil coords thick)
   (make-path-stencil
       `(moveto
           ,(car (list-ref coords 0))
           ,(cdr (list-ref coords 0))
         curveto
           ,(car (list-ref coords 1))
           ,(cdr (list-ref coords 1))
           ,(car (list-ref coords 2))
           ,(cdr (list-ref coords 2))
           ,(car (list-ref coords 3))
           ,(cdr (list-ref coords 3)))
       thick
       1
       1
       #f))

#(define* (make-cross-stencil coords #:optional (thick 0.2) (sz 0.3))
 (ly:stencil-add
   (make-line-stencil 
     thick
     (- (car coords) sz) 
     (- (cdr coords) sz)
     (+ (car coords) sz) 
     (+ (cdr coords) sz))
   (make-line-stencil 
     thick
     (- (car coords) sz) 
     (+ (cdr coords) sz)
     (+ (car coords) sz) 
     (- (cdr coords) sz))))
     
%% Actually this is a special case of `params´ Aaron already defines above.
#(define (zero-to-one-steps parts)
  (iota (1+ parts) 0 (/ 1 parts)))
  
#(define (coord-stils coords)
  (apply ly:stencil-add (map (lambda (cp) (make-cross-stencil cp)) coords)))
  
%% lazy, derived from markup
%% TODO write from scratch
#(define (dotted-line-stencil from to)
 (ly:stencil-translate
   (draw-dotted-line-markup
     $defaultpaper
     (cons
       (list
         '(thickness . 0.5)
         '(on . 0.1)
         '(off . 0.1)
         '(font-encoding . latin1))
         (ly:output-def-lookup $defaultlayout 'text-font-defaults))
          (cons
            (- (car to) (car from))
            (- (cdr to) (cdr from))))
   from))
   
#(define (triangel-to-base-stencil coords)
  (let* ((start (car coords))
         (end (cadr coords)))
    (ly:stencil-add
      (dotted-line-stencil start (cons (car end) (cdr start)))
      (dotted-line-stencil (cons (car end) (cdr start)) end)
      (dotted-line-stencil start end))))

%%%%%%%%%%%%%%%%%%%%%%%
%% EXAMPLES
%%%%%%%%%%%%%%%%%%%%%%%

#(define my-cps '((0 . 0) (10 . 20) (100 . 20) (40 . 50)))

$(let* ((at 0.6)
        (other-at 0.9)
        (the-point (bezier-curve my-cps at))
        (the-other-point (bezier-curve my-cps other-at))
        (bezier-angle-at-point (bezier-angle my-cps at))
        (coords-for-angle (bezier-find-coords-for-angle my-cps at))
        )
     (format #t "\n\tTHE POINT at ~a: ~a" at the-point)
     (format #t "\n\tTHE OTHER POINT at ~a: ~a" other-at the-other-point)
     (format #t "\n\tBEZIER-ANGLE at ~a: ~a" at bezier-angle-at-point)
     (format #t "\n\tAPPROX BEZIER LENGTH between ~a and ~a: ~a" 
       at other-at (bezier-approx-length my-cps at other-at))
     (format #t 
       "\n\tLength of the line connecting THE POINT and THE OTHER POINT: ~a"
       (ly:length 
         (- (cdr the-point) (cdr the-other-point))
         (- (car the-point) (car the-other-point))))
#{
\markup
  \box
  \column {
   \fontsize #2 "Visualizing some Bezier tasks"
   \fontsize #-2 #(format #f "Control-points are: ~a" my-cps)
   \vspace #2
    \fontsize #-2
    \overlay {
      \translate #(car my-cps) \vcenter "  cp1"
      \translate #(cadr my-cps) \vcenter "  cp2"
      \translate #(caddr my-cps) \vcenter "  cp3"
      \translate #(cadddr my-cps) \vcenter "  cp4"
      \translate #the-point \vcenter \halign #RIGHT "the point  "
      \translate #the-other-point \vcenter  "  the other point"
      \translate #(car coords-for-angle) \translate #'(0 . -2) "1. pt for angle"
      \translate #(cadr coords-for-angle) "  2. pt for angle"
      \stencil
        #(ly:stencil-add
           ;; the bezier-curve:
           (make-bezier-stencil my-cps 0.1)
           ;; print `the-point´ where the angle is wished
           (stencil-with-color
             (make-cross-stencil the-point)
             red)
           ;; print `the-other-point´
           (stencil-with-color
             (make-cross-stencil the-other-point)
             red)
           ;; print line directly connecting the-point and the-other-point
           (dotted-line-stencil the-point the-other-point)
           ;; print ref-coords for angle
           (stencil-with-color
             (coord-stils coords-for-angle)
             green)
           ;; print a triangle with those ref-coords to horizontal base
           (triangel-to-base-stencil coords-for-angle)
           ;; print default-cps:
           (stencil-with-color (coord-stils my-cps) cyan))
    }
  }
#})

\markup \vspace #2

\markup
  \box
  \column {
  	\fontsize #2 "For fun a dotted Bezier"
  	\fontsize #-2 #(format #f "Control-points are: ~a" my-cps)
  	\vspace #2
    \stencil
    #(apply ly:stencil-add
      ;(make-bezier-stencil my-cps 0.1)
      (map
        (lambda (x)
          (make-cross-stencil (bezier-curve my-cps x) 0.3 0))
        (zero-to-one-steps 60)))
  }

Attachment: bezier-tests-02.pdf
Description: Adobe PDF document

_______________________________________________
lilypond-user mailing list
lilypond-user@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-user

Reply via email to