Shade3D 公式

34 Cap( 1/3 ) [ Shade Labo ]


#1

34 - 1  概要


Tube 状の自由曲面の端部に cap を被せる script を考えます。( U 方向が開いて Tube 形状になっていなくても構いません )

     

     [ fig 34 - 1 ]



parameter によって、cap 形状にバリエーションを持たせます。

     

     [ fig 34 - 2 ]


#2

34 - 2  Cap 形状


4つの基本量を求めます。
  1. C : 中心座標( 端面ポイント座標の平均 )

  2. Vc : 中心線ベクトル( 端面交差線の接線ベクトルの平均 )

  3. P0 : 先端座標( 中心座標から中心線ベクトルに進む線上に存在 )

  4. Vp : 先端接平面の面法線( 接平面は 端面に平行 / 中心線に垂直 から選択 )



cap 先端座標を **P0**、端面ポイントを **P2** とし、**P2** から交差線接線ベクトルに進む線と cap 先端接平面との交点を **P1** とすると、3点 **P0**, **P1**, **P2** からなる **折れ線に接する楕円弧を求め、cap を形成します**。

     

 [ fig 34 - 3 ]


#3

34 - 3  楕円弧の求め方( その1 )


3 点からなる折れ線に接する楕円弧は、楕円のどの部分を切り取るかによって無数に作り出せます。

          [ fig 34 - 4 ]


そこで切り取りの基準を明確にするために条件を一つ追加します。
* **折れ線の長辺の先端を P0, 中央点を P1, 短辺の先端を P2 とした時、P0 が楕円の短径に接する**
          [ fig 34 - 5 ]
この条件の下で、**円弧を長軸方向に拡大すれば求める楕円弧が得られます**。

          [ fig 34 - 6 ]


#4

34 - 4  楕円弧の求め方( その2 )


関連記事:
  • 19 Custom Smooth ( 2/3 )

[ fig 34 - 6 ] より、与えられた折れ線の条件 **長辺の長さ L1**, **短辺の長さ L2**, **折れ曲がり角 θ** より、**楕円弧の handle 長さ h1**, **h2** を求める式を考えます。

**< 円弧の handle 長さ h >**
円弧を描く bezier 曲線の handle 長さの求め方については **19 Custum Smooth ( 2/3 )** に記しました。

半径 r 見込み角 なる円弧の handle 長さ h は、De Casreljau’s algorythm から、

     

     [ fig 34 - 7 ]



**< 円弧の handle 長さ比 h/L >**
長さ **L** 交差角 **2α** なる折れ線に内接する円弧を bezier line で描くとき、その handle 長さ **h** と **L** との比 **h/L** を **β** で表すと、

     

     [ fig 34 - 8 ]



**< 楕円弧から円弧への scaling factor s >**
長さ **L1**, **L2** 交差角 **θ** なる折れ線に内接する bezier line で描かれた楕円弧を、横方向に **s** 倍すると円弧になり、その円弧は長さ **L** 交差角 **2α** なる折れ線に内接するとします。

この時の scaling factor s を求めると、

          [ fig 34 - 9 ]


     

     [ fig 34 - 10 ]




< 円弧見込み角 2β と 折れ線曲がり角 θ との関係 >

     

     [ fig 34 - 11 ]




< 楕円弧の handle 長さ比 h1/L1, h2/L2 >


以上より、長さ L1, L2 交差角 θ なる折れ線に内接する楕円弧を描くときの handle 長さ比 h1/L1, h2/L2 は次のように与えられます。

     

     [ fig 34 - 12 ]


#5

34 - 5  折れ線に接する楕円弧を作る script


関連記事:
  • 20 Custom Smooth ( 3/3 )

[ script 34 - 1 ] では Shade 上で選択された線形状の最初の3点からなる折れ線に接する楕円を出力します。
[ script 34 - 1 ]
import labo
from vec3 import *


#  oval_arch_line(p0 as vec3, p1 as vec3, p2 as vec3) as vec3 list
#
#	3点の座標 p0, p1, p2 から 楕円弧を表す bezier line を返す

def oval_arch_line(p0, p1, p2) :
	v1 = p1 - p0
	v2 = p1 - p2
	L1 = v1.norm()
	L2 = v2.norm()
	
	if L1 == 0 and L2 == 0 :		#  3点 p0, p1, p2 が同一点
		return [p0, p0, p2, p2]
	else :
		switch = (L2 > L1)
		if switch :					#  L2 > L1 ならば、v1, v2 と L1, L2 を入れ替える
			v1, v2 = v2, v1
			L1, L2 = L2, L1

	t = L2/L1
	cosT = v1.dot(v2)
	bb = (1 - t*cosT)**2
	if bb == 0 :					#  p0, p2 が同一点
		return [p0, p0, p2, p2]
	else :		
		B = 1 + (1 - t**2*cosT**2)/bb
		
	ratio = (4./3)*(1/(1 + B**0.5))
	h1 = ratio*L1					#  楕円弧のhandle 長さ
	h2 = ratio*L2					#  楕円弧のhandle 長さ
	
	if switch :						#  互いに入れ替えた v1, v2 と L1, L2 を元に戻す
		v1, v2 = v2, v1
		L1, L2 = L2, L1
		h1, h2 = h2, h1				#  h1, h2 を入れ替える
	
	outH = p0 + h1*v1
	inH = p2 + h2*v2
	
	return [p0, outH, inH, p2]



scene = xshade.scene()
scene.exit_modify_mode()          

ob = scene.active_shape()
if isinstance(ob, xshade.line) :
	if ob.number_of_control_points >= 3 :
		scene.unsmooth()
		p0 = vec3(ob.control_point(0).position)
		p1 = vec3(ob.control_point(1).position)
		p2 = vec3(ob.control_point(2).position)
		
		bz = oval_arch_line(p0, p1, p2)
		labo.make_bezier_line(xshade, bz, 'oval')	
		
		ob2 = scene.active_shape()
		scene.active_shapes = [ob2, ob]



折れ線に接する楕円弧の作り方は、3次元空間上で交差しない2直線に接する拡張楕円弧の作成に拡張できます。

2直線それぞれの最接近点を結ぶ方向に視線を向けたとき、拡張楕円弧は楕円弧に見えます。

2直線の最接近点座標の求め方は 20 Custom Smooth ( 3/3 ) の [ 式 20 - 1 ] を参照

     

     [ fig 34 - 13 ]


[ script 34 - 2 ] では Shade 上で選択された二つの直線の、それぞれ最初の2点で定義される 2直線に接する拡張楕円弧を出力します。

二つの直線は同一平面上にある必要はありません。

ただし、二直線の最接近点がそれぞれの直線の進む方向上になければ、拡張楕円弧は出力されません。


[ script 34 - 2 ]

import labo
from vec3 import *


#  oval_arch_line_2(p1 as vec3, v1 as vec, p2 as vec3, v2 as vec3) as vec3 list
#
#	2直線 ( 点 p1/p2 を通り u1/u2 に進む直線 ) から 一般化された楕円弧を表す bezier 曲線の handle 座標を返す
#	v1, v2 は単位ベクトル

def oval_arch_line_2(p1, v1, p2, v2) :
	#  t1, t2 :	2直線の最接近点までの p1, p2 からの符合つき距離
	s = v1.dot(v2)	
	u = p2 - p1
	s1 = v1.dot(u)
	s2 = -v2.dot(u)
	
	ss = 1 - s**2
	if ss < 1e-8 :			#  2 直線が平行
		return None
	else :
		L1 = (s2*s + s1)/ss
		L2 = (s1*s + s2)/ss
		if (L1 < 0) or (L2 < 0) :
			return None

	#  ここより以降は、oval_arch_line( ) と同じ ( ただし p0 を p1 に読み替える )
	switch = (L2 > L1)
	if switch :				#  abs(L2) > abs(L1) ならば、v1, v2 と L1, L2 を入れ替える
		v1, v2 = v2, v1
		L1, L2 = L2, L1
	
	t = L2/L1
	cosT = v1.dot(v2)		
	B = 1 + (1 - t**2*cosT**2)/(1 - t*cosT)**2	#  分母 != 0  既に 2 直線の平行判定が行われている
		
	ratio = (4./3)*(1/(1 + B**0.5))
	h1 = ratio*L1			#  楕円弧のhandle 長さ
	h2 = ratio*L2			#  楕円弧のhandle 長さ
	
	if switch :				#  互いに入れ替えた v1, v2 と L1, L2 を元に戻す
		v1, v2 = v2, v1
		L1, L2 = L2, L1
		h1, h2 = h2, h1		#  h1, h2 を入れ替える
	
	outH = p1 + h1*v1
	inH = p2 + h2*v2
	
	return [p1, outH, inH, p2]



scene = xshade.scene()
scene.exit_modify_mode()          

obL = scene.active_shapes
if len(obL) >= 2 :
	if isinstance(obL[0], xshade.line) and isinstance(obL[1], xshade.line) :
		if (obL[0].number_of_control_points >= 2) and (obL[1].number_of_control_points >= 2) :
			obL[0].activate()
			scene.unsmooth()
			p0 = vec3(obL[0].control_point(0).position)
			p1 = vec3(obL[0].control_point(1).position)
			v1 = p1 - p0
			v1.norm()
			
			obL[1].activate()
			scene.unsmooth()
			q0 = vec3(obL[1].control_point(0).position)
			q1 = vec3(obL[1].control_point(1).position)
			v2 = q1 - q0
			v2.norm()

			bz = oval_arch_line_2(p0, v1, q0, v2)
			
			if bz != None :
				labo.make_bezier_line(xshade, bz, 'oval')	
				ob = scene.active_shape()
				obL = [ob, obL[0], obL[1]]
				scene.active_shapes = obL
				
			else :
				scene.active_shapes = obL
				print '前方交差せず'