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))) }
bezier-tests-02.pdf
Description: Adobe PDF document
_______________________________________________ lilypond-user mailing list lilypond-user@gnu.org https://lists.gnu.org/mailman/listinfo/lilypond-user