Shade3D 公式

14 Bezier Surface 接線ベクトル [ Shade Labo ]


#1

14 - 1 精度


関連記事:
  • 13 Bezier Surface 基礎

接線ベクトルを求める際に限界となる状況は、極のある自由曲面の極近傍での極回りの **V** 方向接線ベクトルを求める場合です。

bezier parameter t が t << 1 の元では、極近傍での V 方向線形状は限りなく短くなる上、さらに微小の t の二乗を含む計算で接線ベクトルを求めるので、精度的に限界があります。

          [ fig 14 - 1 ]


bezier line の4つの control point 座標 [ p0, p1, p2, p3 ] 上の parameter t における接線 ベクトル Vt( t )

          [ 式 09 - 01 ]

          [ 式 06 - 02 ]


[ script 14 - 1 ] では 1/8 球 と、ハンドルの方向が単純ではない、2つの自由曲面に対して、bezier parameter s = 0.5 の元で、微小の parameter t における接線ベクトルを求めレポートします。

               [ fig 14 - 2 ]


結果は以下の通りで、t = 1e-81e-9 あたりに精度の限界があり、それよりも小さいと誤差の影響がはっきりと顕れてきます。

この精度の限界を示す指標については 14 - 3 で詳しく検討しますが、parameter 値よりも bounding box size 比 の方が適切です。

     


[ script 14 - 1 ]

import labo
from matrix import *

scene = xshade.scene()

#  sample  bezier surface - 1
h = 10000*4/3*(2**0.5 - 1)
bP1 = []
bP1.append([[0, 10000, 0], [0, 10000, h], [0, h, 10000], [0, 0, 10000]])
bP1.append([[0, 10000, 0], None, None, [h, 0, 10000]])
bP1.append([[0, 10000, 0], None, None, [10000, 0, h]])
bP1.append([[0, 10000, 0], [h, 10000, 0], [10000, h, 0], [10000, 0, 0]])
bP1 = matrix(bP1)
labo.set_bezier_patch(bP1)
labo.make_bezier_surface(xshade, bP1, '<<sample - 1')

#  sample  bezier surface - 2
bP2 = []
bP2.append([[0, 10000, 0], [0, 10000, 7000], [0, -3000, 10000], [0, -10000, 10000]])
bP2.append([[0, 10000, 0], None, None, [5000, -4000, 10000]])
bP2.append([[0, 10000, 0], None, None, [10000, -6000, 5000]])
bP2.append([[0, 10000, 0], [5000, 10000, 0], [10000, 8000, 0], [10000, 2000, 0]])
bP2 = matrix(bP2)
labo.set_bezier_patch(bP2)
labo.make_bezier_surface(xshade, bP2, '<<sample - 2')

k = 0
for bP in [bP1, bP2] :
	k += 1
	print ''
	print 'sample - ' + str(k)
	
	for t in [1e-3, 1e-5,1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 1e-12, 1e-14, 1e-15, 1e-16] :
		Pv = labo.bezier_surface_v_line(bP, t)		#  V 方向線形状
		Vv = labo.bezier_line_tangent(Pv, s)		#  V 方向接線ベクトル
		print 't = ' + str(t) + '   ', Vv

#2

14 - 2 仮想接線ベクトル


微小サイズの parameter に対して精度的な限界があるとしても、実用上そのような範囲での値を必要とするケースは極めてレアでしょう。

しかし、例えば自由曲面上にオブジェクトを配置する場合に、接線ベクトルの方向をオブジェクト方向付けの基準としたい場合があり、存在しない極点での V方向接線ベクトルが欲しくなります。

          [ fig 14 - 3 ]

          曲面上に V 方向接線ベクトル方向に合わせて配置




09 -2 ハンドルが出ていないポイントの接線ベクトル で述べたのと同じアプローチをすれば、精度上の限界という問題を回避しながら、parameter t を限りなく 0 に近づけていったときの極限としての仮想接線ベクトルが求められます。

いま bezier patch [ P ] の第一列が一点集中の 極 O であるとし、t << 1 なる t での曲面上の V方向 bezier line Pv は [ 式 13 - 3 ] より、

          [ fig 14 - 4 ]

          [ 式 14 - 1 ]


ここで、t が限りなく 0 に近づくと t2, t3 は t に比べて急速に 0 に近づくので無視することができ、

          [ 式 14 - 2 ]


つまり、t → 0 とした曲面上の限りなく小さい V方向 bezier line Pv の control point 列は bezier patch [ P ] の第二列と 相似形 となります。

よって、この有意なサイズを持つ bezier line を用いて parameter s での接線ベクトルを求めれば、極における parameter s の仮想接線ベクトルと見なすことが可能です。

14 - 1 で触れたように、t → 0 としたときに t が小さくなるほど顕著な誤差が顕れますが、t = 0 では仮想であっても通常レベルでのまるめ誤差しか含まない真値と呼べる接線ベクトルが得られます。

          [ fig 14 - 3 ]


[ fig 14 - 3 ] で、bezier patch の U方向に極からハンドルが出ていない場合、

          [ 式 14 - 3 ]


となって [ 式 14 - 2 ] の t の項が無くなりますから、[ 式 14 - 1 ] で t**2 の項を残すと、

          [ 式 14 - 4 ]


この場合は、極限での Pv の control point 列は bezier patch [ P ] の第三列と 相似形 となります。

          [ fig 14 - 4 ]


さらに、[ fig 14 - 4 ] で、bezier patch の 極と反対側のU方向ハンドルの先端が極と一致する場合、

          [ 式 14 - 5 ]


となって [ 式 14 - 4 ] の t2 の項が無くなりますから、[ 式 14 - 1 ] で t3 の項を残すと、

          [ 式 14 - 6 ]


この場合は、極限での Pv の control point 列は bezier patch [ P ] の第四列と 相似形 となります。

          [ fig 14 - 5 ]


#3

14 - 3 接線ベクトルを求める関数( step - 1 )


以上を踏まえると、bezier surface の **V** 方向接線ベクトル 求める関数は次のようになります。
[ script 14 - 02 ]
def bezier_surface_v_tangent(bP, s, t) :
	M = labo.bezier_m()	
	T = labo.bezier_t(t, True)
	Pv = bP*M*T									#  V 方向線形状
	Vv = labo.bezier_line_tangent(Pv, s)		#  V 方向接線ベクトル
	
	if Vv.abs2() != 0 :
		return Vv
		
	else :
		if t > 0 and t < 1 :
			return None			#  V  方向につぶれた自由曲面  あるいは U 方向の自己交差による
		
		else :
			if t == 0 :
				L = [1, 2 ,3]
			elif t == 1 :
				L = [2, 1, 0]
			
			for k in L :
				Pv = [bP[0][k], bP[1][k], bP[2][k], bP[3][k]]	#  仮想 V 方向 bezer line
				v = labo.bezier_line_tangent(Pv, s)				#  仮想 V 方向接線ベクトル
				if v.abs2() != 0 :
					return v
			return None											#  V  方向につぶれた自由曲面

#4

14 - 4 精度の限界指標


**14 - 1** で調べたように極近傍で計算される **V** 方向接線ベクトルには精度的な限界がある一方、t = 0 での仮想 **V** 方向接線ベクトルは真値ともいえる値が得られます。

つまり t を 0 に近づけていくと、次第に t = 0 の仮想接線ベクトルに近づき、ある程度以上では誤差が大きくなって逆に離れていき、最終的に t = 0 では真値に落ちつきます。

[ script 14 - 03 ] でその限界点を調べますが、この限界点を表す指標としては bezier parameter t よりも、次の2つの bounding box size の比が適切です。

  • bezier patch の bounding box size
  • 接線ベクトルを求めるための曲面上の V 方向 bezier line の bounding box size

検証結果

bounding box size 比 = 1e-9 あたりが精度の限界指標となっています。

     




[ script 14 - 03 ]

import labo
from matrix import *
import copy


def bezier_surface_v_tangent(bP, s, t) :
	M = labo.bezier_m()	
	T = labo.bezier_t(t, True)
	Pv = bP*M*T								#  V 方向線形状
	
	bb1 = labo.vec3_bounding_box_size(bP)
	bb2 = labo.vec3_bounding_box_size(Pv)
	print ''
	print 't =', t, '     bounding box size ratio = ', bb2[3]/bb1[3]
	
	
	Vv = labo.bezier_line_tangent(Pv, s)	#  V 方向接線ベクトル
	
	if Vv.abs2() != 0 :
		return Vv
		
	else :
		if t >= 1e-5 and t <= 1 - 1e-5 :
			return None					#  V  方向につぶれた自由曲面  あるいは U 方向の自己交差による
		
		else :
			if t < 1e-5 :
				L = [1, 2 ,3]
			elif t > 1 - 1e-5 :
				L = [2, 1, 0]
			
			for k in L :
				Pv = [bP[0][k], bP[1][k], bP[2][k], bP[3][k]]	#  仮想 V 方向 bezer line
				v = labo.bezier_line_tangent(Pv, s)				#  仮想 V 方向接線ベクトル
				if v.abs2() != 0 :
					return v
			return None											#  V  方向につぶれた自由曲面
		
		
			

scene = xshade.scene()

m = 0							#  ブロック No.
n = 0							#  ブロック No.

#  sample  bezier surface - 1
bP1 = []
bP1.append([[0, 10000, 0], [0, 10000, 7000], [0, -3000, 10000], [0, -10000, 10000]])
bP1.append([[0, 10000, 0], None, None, [5000, -4000, 10000]])
bP1.append([[0, 10000, 0], None, None, [10000, -6000, 5000]])
bP1.append([[0, 10000, 0], [5000, 10000, 0], [10000, 8000, 0], [10000, 2000, 0]])
bP1 = matrix(bP1)
labo.set_bezier_patch(bP1)
labo.make_bezier_surface(xshade, bP1, '<<sample - 1')

#  sample  bezier surface - 2
bP2 = copy.deepcopy(bP1)
bP2[0][1][2] = 0
bP2[3][1][0] = 0
labo.set_bezier_patch(bP2)
labo.make_bezier_surface(xshade, bP2, '<<sample - 2')

#  sample  bezier surface - 3
bP3 = copy.deepcopy(bP2)
labo.make_bezier_surface(xshade, bP3, '<<sample - 3')		#  Shade 上に出力し、移動, 回転 , スケール処理
scale = 1000
scene.move_object([0, 0, 0], None, None, [8000*scale, 14000*scale, -10000*scale])
scene.move_object([0, 0, 0], None, [0, -115, 0], None)
scene.move_object([0, 0, 0], None, [40, 0, 0], None)
scene.move_object([0, 0, 0], None, [0, 0, 48], None)
scene.move_object([0, 0, 0], [1.5, 0.7, 0.8], None, None)
bP3 = labo.get_bezier_patch(xshade, 0, 0)					#  Shade 上から bezier patch を読み取り


s = 0.5

print ''
print 'sample - 1'
for t in [1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 0] :
	Vv = bezier_surface_v_tangent(bP1, s, t)
	print Vv
	
print ''
print ''
print 'sample - 2'
for t in [1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 0] :
	Vv = bezier_surface_v_tangent(bP2, s, t)
	print Vv
	
print ''
print ''
print 'sample - 3'
for t in [1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 0] :
	Vv = bezier_surface_v_tangent(bP3, s, t)
	print Vv

#5

14 - 5 接線, 法線ベクトルを求める関数


以上から、t を 0 に近づけていったときに、無視できない誤差が顕れはじめたなら、それ以降は t = 0 での値を採用するというロジックを組み込めば、極および極近傍まですべての parameter 範囲をサポートできるようになります。

次の script がそれであり、 bezier surface の 接線 および 法線を求める一般的な関数として labo に登録してあります。

なお、法線を求める関数では Shade 自由曲面の 切り替え は考慮していません。
もし、xshade.active_shape().flip_face = True ならば、面法線の向きは逆転します。


[ script 14 - 05 ]

#  bezier_surface_u_tangent_2(bP as vec3 matrix, s as float, t as float, epsilon as float = 1e-9) as vec3
#
#	bezier patch bP の parameter s, t における U 方向接線ベクトルを返す

def bezier_surface_u_tangent_2(bP, s, t, epsilon = 1e-9) :
	if bP == None :
		return None
		
	Pu = bezier_surface_u_line(bP, s, True)				#  U 方向線形状	
	bb1 = vec3_bounding_box_size(bP)
	bb2 = vec3_bounding_box_size(Pu)
	ratio = bb2[3]/bb1[3]
	
	if ratio >= epsilon :						
		return bezier_line_tangent(Pu, t)				#  U 方向接線ベクトル
		
	if s >= 1e-3 and s <= 1 - 1e-3 :
			return None			#  U  方向につぶれた自由曲面  あるいは V 方向の自己交差による
		
		else :
			if s < 1e-3 :
				L = [1, 2 ,3]
			elif s > 1 - 1e-3 :
				L = [2, 1, 0]
				
			for k in L :
				Pu = bP[k][:]							#  仮想 U 方向 bezer line
				bb2 = vec3_bounding_box_size(Pu)
				ratio = bb2[3]/bb1[3]
				if ratio >= epsilon :
					return bezier_line_tangent(Pu, t)	#  仮想 U 方向接線ベクトル
					
			return None									#  U 方向につぶれた自由曲面

			
			
		
#  bezier_surface_v_tangent_2(bP as vec3 matrix, s as float, t as float, epsilon as float = 1e-9) as vec3
#
#	bezier patch bP の parameter s, t における V 方向接線ベクトルを返す

def bezier_surface_v_tangent_2(bP, s, t, epsilon = 1e-9) :
	if bP == None :
		return None
		
	Pv  = bezier_surface_v_line(bP, t, True)				#  V 方向線形状	
	bb1 = vec3_bounding_box_size(bP)
	bb2 = vec3_bounding_box_size(Pv)
	ratio = bb2[3]/bb1[3]
	
	if ratio >= epsilon :									
		return bezier_line_tangent(Pv, s)					#  V 方向接線ベクトル
		
	else :
		if t >= 1e-3 and t <= 1 - 1e-3 :
			return None			#  V  方向につぶれた自由曲面  あるいは U 方向の自己交差による
		
		else :
			if t < 1e-3 :
				L = [1, 2 ,3]
			elif t > 1 - 1e-3 :
				L = [2, 1, 0]
				
			for k in L :
				Pv = [bP[0][k], bP[1][k], bP[2][k], bP[3][k]]		#  仮想 V 方向 bezer line
				bb2 = vec3_bounding_box_size(Pv)
				ratio = bb2[3]/bb1[3]
				if ratio >= epsilon :
					return bezier_line_tangent(Pv, s)				#  仮想 V 方向接線ベクトル
					
			return None												#  V 方向につぶれた自由曲面
			
			
			
#  bezier_surface_normal_2(bP as vec3 matrix, s as float, t as float, epsilon as float = 1e-9) as vec3
#
#	bezier patch bP の parameter s, t における 法線ベクトルを返す
#	Shade 自由曲面の 切り替え は考慮していない

def bezier_surface_normal_2(bP, s, t, epsilon = 1e-9) :
	if bP == None :
		return None

	Vu = bezier_surface_u_tangent_2(bP, s, t, epsilon)
	Vv = bezier_surface_v_tangent_2(bP, s, t, epsilon)
	
	if Vu == None or Vv == None :
		return None
	else :
		Vn = Vu*Vv
		Vn.norm()
		return Vn

次の script は、Shade 上で選択した自由曲面の区間 [ 0, 0 ] の bezier parameter s = 0, 0.25, 0.5, 0.75, 1, t = 0, 0.25, 0.5, 0.75, 1 での **U**, **V** 方向接線ベクトルと法線ベクトルを求め、そのマーカーを線形状として出力します。

[ script 13 - 6 ] とは異なり、極においてもマーカーが表示されます。


[ script 14 - 6 ]

import labo

scene = xshade.scene()

m = 0							#  ブロック No.
n = 0							#  ブロック No.

bP = labo.get_bezier_patch(xshade, m, n)
bb = labo.vec3_bounding_box_size(bP)
r = bb[3]/16									#  マーカーサイズ

scene.create_part('接線, 法線')

for s in [0, 0.25, 0.5, 0.75, 1] :
	for t in [0, 0.25, 0.5, 0.75,  1] :
		p = labo.bezier_surface_position(bP, s, t)		#  座標値
		Vu = labo.bezier_surface_u_tangent_2(bP, s, t)	#  U 方向接線ベクトル
		Vv = labo.bezier_surface_v_tangent_2(bP, s, t)	#  V 方向接線ベクトル
		Vn = labo.bezier_surface_normal_2(bP, s, t)		#  法線ベクトル

		L = [p, p + r*Vu, p + r/2*Vv, p, p + r*Vn]		#  マーカー

		labo.make_simple_line(xshade, L, False, 's = ' + str(s) + '     t = ' + str(t))
		
scene.select_parent(1)