Am Di., 16. Apr. 2019 um 23:45 Uhr schrieb Aaron Hill <[email protected]>: > > On 2019-04-16 10:37 am, Thomas Morley wrote: > > Am Mo., 15. Apr. 2019 um 19:26 Uhr schrieb Lukas-Fabian Moser > > <[email protected]>: > >> > >> 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 [email protected] https://lists.gnu.org/mailman/listinfo/lilypond-user
