ふくらみを持ったポリラインから、円弧情報を得る

# ふくらみを持ったポリラインとは？
AutoCAD では、ポリラインに円弧の曲線を含ませることができます。
「ポリラインって通常は**連続直線**の事でしょ？」
と思われるかもしれませんが、そういう事が出来る仕組みなのです。
## ふくらみを持ったポリラインの実物
ふくらみを持ったポリラインで作図された図形の例は、下図の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2555080/af977eb4-fba3-8a66-fa24-4c6a91f4f51f.png)
このふくらみを持ったポリラインのエンティティ情報は、下記の通りです。
```LISP:ふくらみを持ったポリラインのエンティティ情報
(0 . "LWPOLYLINE")
(100 . "AcDbEntity")
(67 . 0)
(410 . "Model")
(8 . "0")
(100 . "AcDbPolyline")
(90 . 4)
(70 . 0)
(43 . 0.0)
(38 . 0.0)
(39 . 0.0)
(10 8.45388 11.8955); 1点目座標
(40 . 0.0)
(41 . 0.0);　　↑
(42 . 0.0); ← ふくらみ(bulge)がゼロなので、この間は直線
(91 . 0);　　　↓
(10 15.2163 13.7794); 2点目座標
(40 . 0.0)
(41 . 0.0);　　　　　↑
(42 . -0.279508); ← ふくらみ(bulge)がゼロではないので、この間は円弧
(91 . 0);　　　　　　↓
(10 21.081 12.1347); 3点目座標
(40 . 0.0)
(41 . 0.0)
(42 . 0.0)
(91 . 0)
(10 26.1977 5.88499); 4点目座標
(40 . 0.0)
(41 . 0.0)
(42 . 0.0)
(91 . 0)
(210 0.0 0.0 1.0)
```
エンティティ名やハンドル等の、オブジェクト固有のエンティティは、削除しています。
ふくらみを持ったポリラインの円弧部分の情報は
+ 始点座標
+ 終点座標
+ ふくらみ

の3つです。
この始終点とふくらみの情報を元に、円弧のエンティティを生成したいのです。
## 円弧のエンティティ情報
対する円弧オブジェクトの情報について。
実際に`arc`コマンドで円弧を作図する際は
+ 始点、中間点、終点の3点を指定する方法
+ 中心点、始点、終点の3点を指定する方法(終点の代りに角度や弦長で指定も可能)

等がありますが、作図方法に関わらず実際の図形のエンティティ情報は、下記の様になっています。
```LISP:円弧のエンティティ情報
(0 . "ARC")
(100 . "AcDbEntity")
(67 . 0)
(410 . "Model")
(8 . "0")
(100 . "AcDbCircle")
(10 36.4711 12.3291 0.0); 中心点
(40 . 3.64553); 半径
(210 0.0 0.0 1.0)
(100 . "AcDbArc")
(50 . 2.72348); 中心点から見た円弧始点の角度(ラジアン)
(51 . 6.22984); 中心点から見た円弧終点の角度(ラジアン)
```
始点から終点に向けて、**反時計回り**で作図されます。
円弧を作図するうえで必要なデータは
+ 中心点
+ 半径
+ 始点角度
+ 終点角度

の4つです。
## ふくらみとは？
ふくらみとは何ぞや？ という問いに答えられるほど、私の知識は豊富ではありません。
算数を少しかじった程度です。
ですので、下記の情報が非常に参考になりました。

https://qiita.com/tydesign/items/cab9d9cf6a625204707c

ふくらみとは何ぞや？はこのサイトで理解して頂ければと思います(無責任ですみません)。
少なくとも私が理解できたのは
**ふくらみがマイナスの場合は時計回りの円弧になるから、始終点を入れ替えないと正しい円弧情報にならない**
ということぐらいです。
# 実際に円弧情報を取得してみよう
### 最初に考えた、頭悪いやり方
という訳で、いきなり LISP のコードです。
引数として
+ 始点座標
+ 終点座標
+ ふくらみ

を渡すことで
+ 中心座標
+ 半径
+ 始点角度
+ 終点角度

をリストで返します。
```LISP:始終点とふくらみから円弧情報を取得(旧版)
;始終点とふくらみから円弧情報(中心座標、半径、開始角度、終了角度)を取得
(defun bulge_to_arc (
  s_point ;始点座標
  e_point ;終点座標
  bulge ;ふくらみ
  /
  s_temp
  e_temp
  ang_temp
  dist_temp
  r_dist
  d_point
  c_point
  )

  (if (minusp bulge)
    (setq s_temp e_point e_temp s_point)
    (setq s_temp s_point e_temp e_point)
  );if
  (setq
    ang_temp (atan (abs bulge))
    dist_temp (/ (distance s_temp e_temp) 2.0)
    r_dist (/ (* dist_temp (+ 1.0 (expt bulge 2.0))) (* 2.0 (abs bulge)))
  );setq
  ;円弧の中心座標取得
  (setq
    d_point (inters s_temp (polar s_temp (- (angle s_temp e_temp) ang_temp) 1.0) e_temp (polar e_temp (+ (angle e_temp s_temp) ang_temp) 1.0) nil)
    c_point (polar d_point (+ (angle s_temp e_temp) (/ PI 2.0)) r_dist)
  );setq
  (list c_point r_dist (angle c_point s_temp) (angle c_point e_temp))

);defun
```
なんか**もっとスマートに算出できる方法**がありそうですが、私の頭ではこれが限界でした・・・
半径「r」の算出は、先の参考サイトの式をそのまま流用しています。
面倒だったのは中心座標を求める所。参考サイトの「D点」を求めてから、始終点角度に垂直な「r」の距離の点を求める方法で算出しています。
ここはもっと上手いやり方が有りそうです。
始終点角度は中心座標が分かれば簡単です。
### もっと良いやり方
先に紹介したサイトにもっとスマートに算出するヒントがあったのに、私が理解していませんでした。
反省も込めて、解説。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2555080/fdd2544c-6046-27d3-2f3a-15c9e8bfbfa6.png "図1")図1
先ず、大前提として「ふくらみ(_bulge_ 以下 _b_)」の定義ですが
_b = tan ( θ / 4 )_ ・・・①
となります。
半径 _r_ は先の参考サイトにある通りなので省略します。
先の**頭の悪いやり方**は _AD_ 方向の長さが適当な線分と、_BD_ 方向の長さが適当な線分の交点座標を`inters`で求め、そこから`polar`で中心座標を求めるやり方でしたが、**交点を求める**というのが如何にも頭が悪いやり方だと思ったのです。
しかし、_DE_ の距離が分かれば、_EC_ の距離もわかるので、その方法で中心座標を求めた方がスマートです。
で、_DE_ 間の距離は既に参考サイトに _s_ として記載されているんですが
_tan ( θ / 4 ) = DE / AE_ ・・・②
①と②から
_b = DE / AE_
_DE = b * AE_
_AE_ の長さは、_AB_ の長さの半分、_E_ の座標は _A_ と _B_ の中点
どちらも _A_ と _B_ の座標が分かっていれば簡単に求められます。
距離は三平方の定理を使わずとも、AutoLISP なら`distance` で簡単に求められます。
_EC_ 間の距離は
_EC = r - DE_
ですから、ここまでわかれば`polar`で中心座標が求められます。
という訳で、改修したコードです。
```LISP:始終点とふくらみから円弧情報を取得
;始終点とふくらみから円弧情報(中心座標、半径、開始角度、終了角度)を取得
(defun bulge_to_arc (
  s_point ;始点座標
  e_point ;終点座標
  bulge ;ふくらみ
  /
  s_temp
  e_temp
  ang_temp
  dist_temp
  r_dist
  d_point
  d_dist
  c_point
  )

  (if (minusp bulge) ;ふくらみがマイナスの場合、時計回りなので始点と終点を入れ替える
    (setq s_temp e_point e_temp s_point)
    (setq s_temp s_point e_temp e_point)
  );if
  (setq
    ang_temp (atan (abs bulge)) ; θ/4 を求める
    dist_temp (/ (distance s_temp e_temp) 2.0) ; AE 間の距離を求める
    r_dist (/ (* dist_temp (+ 1.0 (expt bulge 2.0))) (* 2.0 (abs bulge))) ; 半径を求める
  );setq
  ;円弧の中心座標取得
  (setq
    d_dist (* (abs bulge) dist_temp) ; DE 間の距離を求める
    ; E の座標を求める
    d_point (list (/ (+ (car s_temp) (car e_temp)) 2.0) (/ (+ (cadr s_temp) (cadr e_temp)) 2.0))
    c_point (polar d_point (+ (angle s_temp e_temp) (/ PI 2.0)) (- r_dist d_dist)) ;中心点座標を求める
  );setq
  (list c_point r_dist (angle c_point s_temp) (angle c_point e_temp))

);defun
```
もやもやしてた部分が、ようやくスッキリしました。
# 終わりに
今までふくらみってよく分からないな、と思っていたのですが、今回なんとなく理解できてよかったです。
まだ完全に理解しきれていないので、もっと精進しないといけないと思いました。