Hatena::ブログ(Diary)

分室の分室 このページをアンテナに追加

2016-10-18 Tue

往復運動(レシプロ運動)を表現する連番リストを作る(srfi-1 iota の変種)

【584】


増加・減衰を繰り返すグラディエント部品を作る:

 例えば GIMP で、ブラシサイズ(太さ)が描画の度に次第に太くなったり細くなったりする『ブラシ動的サイズ(グラディエント)』というプラグインを作るとすれば…

 ・グラディエントの範囲:0〜MAX(20 or 40) ←ブラシ太さの初期値による
 ・増加グラディエントが MAX を超えると減衰に転じる (MAX - MAX超過分)
 ・減衰グラディエントが 0 未満になると増加に転じる (abs を使うのが簡単?)
 ・グラディエントのステップ幅は± 2, 3, 5, 10 の 4通り(等間隔)とする

…このようなグラディエントが考えられる。今回の肝は、有効範囲を超えたら自動的に『増加・減衰』に転じる仕組みを作ること(有効範囲から外れないようにする)。

 これがあれば、色(グラデーション)やサイズはもちろん、座標で『壁に当たって跳ね返る等速運動のボール』や『鏡に当たって反射する光線の軌跡』なども表現できる。そうなると『リフレクタル(反射)グラディエント』とでも名付けるべきかもしれない。

 単純に modulo を使うと『0, … MAX-1, 0, … MAX-1, …』を繰り返す『循環(片道反復)グラディエント』(一方通行)となるので、それに対して『巻き戻し(往復)グラディエント』と名付けるのもアリな気がする。『反射』と『往復』では、往復の方がシックリくるかなぁ。


行ったり来たりの往復運動を表現する手法は色々ありそうだ…

 『増加』の場合、MAX の超過分というのは引算で表せるが、それでは『行ったり来たり』の往復運動が表せない。で、『割り算の剰余』と『整数の商』を組み合わせるのが分かり易いかなぁ…と思う。つまり、商が奇数か偶数かの場合分けで往路・復路の判定をする作戦。

 『減衰』の場合、負数の絶対値を使えば簡単だが、そのままでは 1往復しかできない。ループの範囲内なら、何往復でも出来なければ面白くない。

 行ったり来たりの往復運動を延々に表現するなら、やはりそこは再帰でしょ…と、Schemer, Lisper は考えるに違いない。単純に考えても、往路と復路で 1回ずつ副作用を実行すれば『巻き戻し』の効果がある。多重再帰だと、もっと高度なことができそうな気がするが…自分には難しいので以下省略。

 コードの作りとしては、iota の変種っぽくして一気に連番リストを作ってしまう方式と、ループ中から毎回呼び出される方式が考えられる…。Script-Fu で実行する場合、描画時のループ回数は不明な方が多いと思う。座標データ生成時は可変長リストに甘えて、いちいちリスト長を数えてはいないので、一気に連番リストを作るのは無理がありそうだ。…が、このコードなら割と簡単に書けそうな気がする。


 まずは練習ということで、iota なら以前に亜種を作ったことがあるので、iota の変種的なものを組んでみる。↓

;; ******************************************************************
;; reciprocating-motion            (往復運動(レシプロ運動)を表現)
;;                                 (srfi-1 iota の変種(独自仕様))
;; ******************************************************************
;; 書式:(reciprocating-motion count start step upper-limit [shift])
;; 引数:count      :ループ回数(リスト長)
;;       start      :最初のデータ(初期値)
;;       step       :最初の間隔(増減幅…等間隔限定)
;;       upper-limit:上限値(折り返し点)
;;       shift      :シフト幅(下限値を 0 以外にする…省略可)
;;
;; 補足:srfi-1 iota の変種といえる(引数 count start step までは同じ)。
;;       違うのは、増加中に上限値を超えた場合に折り返して減衰に転じる、
;;       逆に減衰中に 0 未満になった場合も折り返して増加に転じること。
;;       下限値は常に 0。…代わりに shift で計算結果を加減できるように
;;       している(この場合、上限値も連動シフトすることになる…)。
;;
;; 備考:往復グラディエント(gradient-both-ways)、巻き戻し、反射、
;;       膨張or収縮…などの動的表現(dynamics)に使えると思う。
;;
;; 参考(英語):
;;  往復      → both ways;  to-and-fro;  reciprocating;  round trip
;;  上限値    → upper limit;  upper bound;  upper value
;;  折り返し点→ turning point;  turn around point;  turning back point
;;
(define (reciprocating-motion count start step limit . lis-shift)
  (let ((shift (if (null? lis-shift) 0 (car lis-shift))))
    (cond
      ((zero? count) '())
      ;limit 超過したら→ (- limit (- start limit)) で折返し、
      ;                   (* step -1) で向きを反転
      ((< limit start)
        (cons (+ (- limit (- start limit)) shift)
          (reciprocating-motion
            (- count 1)
            (+ (- limit (- start limit)) (* step -1))
            (* step -1) limit shift)))
      ;0 未満になったら→ (abs start) で折返し、
      ;                   (* step -1) で向きを反転
      ((> 0 start)
        (cons (+ (abs start) shift)
          (reciprocating-motion
            (- count 1)
            (+ (abs start) (* step -1))
            (* step -1) limit shift)))
      (else
        (cons (+ start shift)
          (reciprocating-motion
            (- count 1) (+ start step) step limit shift))) )))

; 実行例:
;         ;# count:20, start:5, step:3, limit:30
;   gosh> (reciprocating-motion 20 5 3 30)
;   (5 8 11 14 17 20 23 26 29 28 25 22 19 16 13 10 7 4 1 2)
;
;         ;# count:20, start:5, step:3, limit:30, shift:50
;   gosh> (reciprocating-motion 20 5 3 30 50)
;   (55 58 61 64 67 70 73 76 79 78 75 72 69 66 63 60 57 54 51 52)
;
;   gosh> (reciprocating-motion 20 5 -5 30)
;   (5 0 5 10 15 20 25 30 25 20 15 10 5 0 5 10 15 20 25 30)

…割とすんなり書けた。今回は単純再帰で記述(コンパクトに記述できるので)。末尾再帰なら、最後に reverse する方式が無難だと思う。


 このコードが使えるのは、ループ回数が自明の場合に限る。あとは map を使って処理するのが常道パターンになると思う。Script-Fu の場合、ループ回数を特定してから GIMP 副作用を実行すると、ループが二度手間になってしまうので、それは効率がやや劣るかも…。

 なので、ループ中から毎回呼び出される手続も欲しいところ。その場合、呼出元プロシージャで定義した『上位レベル定義変数』が必要になると思う。カレントの step が増加中なのか減衰中なのかを set! で格納する仕組み…。ただ、二度手間ループのコストと、ループ中で毎回 set! するコスト…コスト的には大差ないかもしれない(ソースが冗長か簡潔かの違いが目立つだけかもしれない)。何はともあれ、次回、そのコードを作ってみる予定。(つづく)