Shade3D 公式

32 太さの変わる掃引(2/3 ) [ Shade Labo ]


#1

32 - 1  交差方向線形状の handle 方向( その1 )


関連記事:
  • 17 Smooth
  • 31 太さの変わる掃引( 1/3 )

まず、水平掃引や曲面に沿った掃引と同様に、**掃引断面は中心線に対して垂直に位置する** ことを前提とします。

太さの変わる掃引では断面サイズが変化するので、その断面交差線の handle 方向 Vt は中心線の接線ベクトル U に対して傾きます。

     

     [ fig 32 - 1 ]



この断面交差線の handle 方向 **Vt** の **U** に対する傾きの求め方を考えるために、次のようにモデルを単純化します。

     

     [ fig 32 - 2 ]



ここで、ためしに交差方向に smooth を掛けて四角形の掃引断面を横から眺めると、中央とコーナーの handle が一致しません。

     

     [ fig 32 - 3 ]



これらが下図のように一致するために必要とされる、断面交差線 handle 方向に備わるべき条件について考えます。

          [ fig 32 - 4 ]



コーナー部分の交差線を掃引中心線回りに回転し、中央部分の交差線と並べたものを横から見ます。

     

     [ fig 32 - 5 ]



[ fig 31 - 1 ] より、交差方向 handle 長さは 基準面 pL1 で区切られますが、このケースでは pL1 は掃引中心線に垂直です。

よって下図 handle H1, H2 の中心線方向の長さ h1 と h2 は必ず等しくなります。

          [ fig 32 - 6 ]



handle **H1** とθ傾けた **H2** とが横から見て一致するためには、下図の高さ d1 と d2 が r に比例しなければなりません。

          [ fig 32 - 7 ]



滑らかな曲線を与える smooth 処理と、**高さ d1, d2 が r に比例する** という条件を両立するために、次のように handle 方向を処理します。
  1. 隣接3点 p0, p1, p2 の X, Y 座標を規格化

  2. smooth ロジックによる中央 P1 の handle 方向を求め

  3. 中心線からの距離に比例させながら元のプロポーションに戻す


具体的な処理内容を下図に示します。

[ fig 32 - 6 ] の コーナー交差線 と 中央交差線 は規格化処理によって同一形状に変換されますから、最終的に得られる handle 方向の傾きの Y 方向の高さ d1, d2 は下図に従って r に比例させることができ、[ fig 32 - 4 ] のように綺麗に整った形状が得られます。

     

     [ fig 32 - 8 ]


          [ fig 32 - 9 ]

次のような片方にしか handle が伸びない、あるいは handle が折れるケースは terminal handle として扱います。
  • 掃引中心線が開いている場合の始端 / 終端
  • コーナー
  • 重点によって片方にしか handle が伸びないポイント

**r に比例する** という条件から、terminal handle のベクトル **Vh** に **17 Smooth** で述べた mirror handle は適用できません。

そこで次のような簡便な方法を用います。

     

     [ fig 32 - 10 ]


          [ fig 32 - 11 ]

#2

32 - 2  交差方向線形状の handle 方向( その2 )


前項で 直線状に配置されたスケールの掛かった断面の交差方向線形状の傾きの求め方を述べました。

これを曲線状の掃引中心線に対して適用するには、次のような手順になります。

  1. 当該配置断面とその前後の配置断面を、当該ポイントの距離をベースに直線状に配置

  2. [ fig 32 - 8 ], [ fig 32 - 10 ] より、交差方向線形状の handle ベクトル Vh を求め、

  3. Vh を元形状に Vt としてフィードバックし、交差線形状の handle 方向とする



掃引中心線が曲がっている場合、配置距離を中心線のポイント間距離と同じに見なすのでは不十分です。

当該ポイントが曲がりの内側にあれば短く、外側にあれば長くなります。

これをどのように算定するかは難問ですが、[ fig 32 - 8 ] が示すように、当該ポイントの距離は、前後区間の距離のバランスがとれていれば、あまり厳密な長さまでは必要ありません

そこで次のように簡易的な算定を行います。

  • 当該断面サイズで一様な太さの掃引体を作成した場合に得られる仮想 handle の長さ hL を求める

  • 掃引中心線の handle 長さを uL とする

  • scaling factor s = hL / uL とし、掃引中心線のポイント間距離にこれをを乗じたものを、直線状に配置する際の断面配置距離とする



仮想 handle 長さ **hL** が 0 になると、断面配置距離も 0 になってしまい、smooth handle の方向 **Vh** が求められません。

また、0 にならなくても小さな値になると、断面配置距離も小さくなり、[ fig 32 - 8 ] で求める Vh の傾きが極端になって大きな歪みを生じる可能性があります。

このため、scaling factor s には limitter 0.1 を与えます。

     

     [ fig 32 - 12 ]


          [ fig 32 - 13 ]


#3

32 - 3  交差方向線形状の handle 方向( その3 )


**太さの変わる水平掃引**, **太さの変わる面に沿った掃引** では強制ねじれによる傾きが加わりますが、全く同様にハンドリングできます。

     

     [ fig 32 - 14 ]


          [ fig 32 - 15 ]
          [ fig 3 - 16 ]

#4

32 - 4  交差方向線形状の handle 方向( その4 )


handle ベクトル **Vh** を求めることに関連した関数を [ script 32 - 1 ] , [ script 32 - 2 ] に記します。

関数 get_vh_A( ) が [ fig 32 - 8 ] の処理に、関数 get_vh_B( ) が [ fig 32 - 10 ] の処理に相当します。


[ script 32 - 1 ]                         [ fig 32 - 8 ] の処理
#  get_vh_A(L1 as float, L2 as float, r0 as float, r1 as float, r2 as float) as vec3
#
#	scaling に伴う交差方向線形状 handle の傾きを表すベクトルを返す
#
#	L1, L2 :		X 方向長さ ( scaling factor は適用済み )
#	r0, r1, r2 :	Y 方向長さ

def get_vh_A(L1, L2, r0, r1, r2) :
	#  3点のポイントの X, Y size を規格化したものに smooth をかけた smooth vector を作り、
	#  その Y size を original の proportion に戻す
	
	#  x0, x1, x2, y0, y1, y2 : 3点のポイントの X, Y size を規格化
	sx = 1/(L1 + L2)
	x0 = 0
	x1 = L1*sx
	x2 = 1
	
	r = (abs(r1 - r0) + abs(r2 - r1))
	if r == 0 :						#  r0 = r1 = r2 = 0 であり、断面ポイント座標と掃引中心線の配置ポイント座標が一致している
		return vec3(1, 0, 0)
	else :
		sy = 1/r
	y0 = 0
	y1 = (r1 - r0)*sy
	y2 = (r2 - r0)*sy
	
	#  v : 規格化された3点の中央の point に smooth をかけて得られる smooth vector
	p0 = vec3(x0, y0, 0)
	p1 = vec3(x1, y1, 0)
	p2 = vec3(x2, y2, 0)

	u0 = p0 - p1
	u2 = p2 - p1
	a0 = u0.norm()
	a2 = u2.norm()
	v = a0*u2 - a2*u0

	#  smooth vector w の Y size を original の proportion に戻す
	vh = vec3(v[0], v[1]*sx/sy, 0)
	vh.norm()
	
	return vh

[ script 32 - 2 ]                         [ fig 32 - 10 ] の処理
#  get_vh_B(L1 as float, L2 as float, r0 as float, r1 as float, r2 as float) as vec3
#
#	開いた線形状の terminal 点における scaling に伴う交差方向線形状 handle の傾きを表すベクトルを返す
#
#	L1, L2 :		X 方向長さ ( scaling factor は適用済み )
#	r0, r1, r2 :	Y 方向長さ

def get_vh_B(L1, L2, r0, r1, r2) :
	tan1 = (r1 - r0)/L1
	tan2 = (r2 - r1)/L2
				
	if abs(tan2) < abs(tan1) :
		a = 2
	else :
		a = 3
		
	tan0 = tan1 - (tan2 - tan1)/a
	vh = vec3(1, tan0, 0)
	vh.norm()
	
	return vh

#5

32 - 5  Scale 値 = 0 への対応


直線上に配置した掃引断面からサイズの規格化を経由して求めた smooth handle の方向を元の形状にフィードバックする際、次のことに留意する必要があります。
下図において、**W** = **Q** の場合、quaternion **Qg** は求まりません。

W = Q 即ち、掃引断面のポイント座標 W が中心線の配置位置座標 Q と一致するケースとしては次の2つが考えられます。

  1. 元々 scaling を施す前の WQ と一致していた

  2. scale = 0 なる scaling で WQ が一致してしまった


1)のケースではいかなる値の scaling を受けても、交差 handle 方向 **Vt** は仮想 handle の方向に一致するとみなすので、**Qg** を求める必要がなく問題ありません。

2)のケースでは Qg が求まらないので、VhVt としてフィードバックできない問題が生じます。


script ではこれに対応するため、scaling を施した掃引断面と施す前の掃引断面の二つを作成しておき、後者から W - Q を求めるようにしています。

     

     [ fig 32 - 17 ]


#6

32 - 6  コーナーでの仮想 handle 長さ


仮想 handle 長さを利用した断面交差方向 handle の方向と長さの求め方には極端な形状の歪みを避けるために scaling factor s に limitter 0.1 を与えていますが、それでもコーナー部分に対してはやや無理な所があります。

コーナー部分の交差線は通常部分に対し、コーナー外側では引き延ばされ、内側では縮められるためです。


特に内側仮想 handle 長さが問題で、あまり短くなるとそれに続く仮想 handle 長さとの兼ね合いから、[ fig 32 - 8 ] で求める smooth handle の傾きが極端になり、掃引形状に大きな歪みが生じることがあります。

これを避けるために仮想 handle 長さ hL はコーナー外側と内側とで定義を変え、内側の仮想 handle 長さを長くします。

     [ fig 32 - 18 ]


          [ fig 3 - 19 ]



仮想 handle の長さを求める関数 handle_length( ) を [ script 32 - 3 ] に記します。
[ script 32 - 3 ]
#  handle_length(w as vec3, u as vec3, q as vec3, r as float, p as vec3, vp as vec3, corner as bool) as float
#
#	dummy handle 長さを返す
#
#	w :	断面ポイント座標
#	u :	掃引中心線接線ベクトル
#	q :	掃引中心線 point 座標
#	r :	掃引中心線 bounding box size / 16
#	p :	掃引中心線の handle 座標
#	vp :	基準面法線
#	corner :	corner flag

def handle_length(w, u, q, r, p, vp, corner) :
	#     L :  点 w を通り、u 方向に進む直線
	#	PL0 : 点 q を通り面法線 u なる面
	#	PL1 : 点 p を通り面法線 vp なる面
	#	精度確保のため r = 1 とはせず、r = bounding box size / 16 としてある

	d1 = vp.dot(w - p)			#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*u - p)	#  点 w + r*u と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)			#  点 w を通り u 方向に進む直線と、面 PL1 との交点までの符号つき距離
	
	if corner :
		a = u.dot(w - q)		#  点 w と面 PL0 との符号つき距離	
		if a > 0 :
			d += a				#  d が長くなる場合のみ補正を行う
	
	return abs(d)

#7

32 - 7  歪みの回避


scaling のない従来の掃引では、下図のように断面ポイント **W** が中心線ポイント **Q** から見て基準面の外側にあれば必ず食い込み( handle 反転 )が生じますが、太さの変わる掃引では **食い込みにならずに大きな歪みが生じることがあります**。

このような歪みは コーナー / 非コーナー いずれでも生じます。


この歪みを抑えるために、断面ポイント W が基準面の外側にあれば食い込み時と同様に handle 長さを 1/12 にします

1/12 でも不十分でもっと短くしなければならない状況も考えられますが、scaling factor s に limitter 0.1 を与えてあるので、よほど極端な場合でなければ起こりえないでしょう。

たとえそのようなレアケースが生じても、そもそもそのような極端に変化する形状では歪みを抑えることは不可能と思われます。

     

     [ fig 32 - 20 ]



交差 handle の長さを求める関数 sweep_handle_x( ) を [ script 32 - 4 ] に記します。
[ script 32 - 4 ]
#  sweep_handle_x(w as vec3, vr as vec3, r as float, p as vec3, vp as vec3) as vec3
#
#	handle 座標を返す

def sweep_handle_x(w, vr, r, p, vp) :
	#     L :  点 w を通り、vr 方向に進む直線
	#	PL1 : 点 p を通り面法線 vp なる面
	#	精度確保のため r = 1 とはせず、r = bounding box size / 16 としてある

	d1 = vp.dot(w - p)			#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*vr - p)	#  点 w + r*vr と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)			#  点 w を通り vr 方向に進む直線と、面 PL1 との交点までの 符号付き距離
		
	if d >= 0 :
		if d1 > 0 :				#  点 w が基準面 PL1 の外側にある
			d /= 12	
	else :
		d /= -12			
		
	return w + d*vr

#8

32 - 8  断面配置間隔の調整


太さの変わる掃引 c_sweep_x( ) のベースとなっている c_swee( ) では、指定する最大曲がり角によって掃引中心線を自動分割します。

しかし掃引中心線にほとんど曲がりのない長い bezier 区間があると、その区間にどんな scaling の変化があっても断面配置は行われません。

これを回避するために一連の分割処理の最後に一つ分割工程を追加します。

  1. 屈曲点による分割

  2. 曲がりに応じた分割

  3. 長い bezier 区間を分割

          1), 2)の行程を経て分割された掃引中心線の各 bezier 区間毎の長さを求め、平均長さを求める

          平均長さの 1.5 倍を超える長さをもつ bezier 区間があれば、1.5 倍以内に納まるように分割


          [ fig 32 - 21 ]



[ script 32 - 5 ] はこの部分を抜粋したものです。
[ script 32 - 5 ]
#  bZs の各区間を parameter 基準で8等分して長さの概算値を求め、区間長の平均の1.5倍の長さのある区間を分割する
	Ls = []
	totalL = 0.
	kk = 0
	for bz in bZs :
		L = 0.
		p1 = bz[0]
		for i in range(1, 9) :
			t = float(i)/8
			p2 = labo.bezier_line_position(bz, t)
			v = p2 - p1
			L += v.abs()
			p1 = p2
		Ls.append(L)				#  区間長
		totalL += L
		if L == 0 :
			kk += 1
		
	ns = len(bZs)
	maxL = 1.5*totalL/(ns - kk)		#  区間長の平均値 x 1.5	( 長さ 0 の区間は平均長さの算定に加えない )
	
	bZs3 = []
	insert_list = []
	k = 0
	for i in range(ns) :
		k += 1
		kk = i + 1
		if kk > nop - 1 :
			kk = 0
			
		if (Ls[i] <= maxL) or (not has_out_handle[i] and not has_in_handle[kk]) :
			bZs3.append(bZs[i])
		else :
			nj = int(Ls[i]/maxL) + 1		#  分割数
			param1 = 0.
			for j in range(nj) :
				param2 = float(j + 1)/nj
				bZs3.append(labo.bezier_line_subdivision(bZs[i], param1, param2))
				param1 = param2
				insert_list.append(k)
				nop += 1
			insert_list.pop()
			nop += -1
			
	if len(insert_list) > 0 :
		insert_list.reverse()
		for k in insert_list :
			has_in_handle.insert(k, True)
			has_out_handle.insert(k, True)
			linked.insert(k, True)
				
	bZs = bZs3