Shade3D 公式

19 Custom Smooth( 2 / 3 ) [ Shade Labo ]


#1

19 - 1  円形


関連記事:
  • 17 Smooth
  • 18 Custom Smooth ( 1/3 )

smooth の処理方法を規定する [ 条件 17 - 1 ] の第二項について考えます。
[ 条件 17 - 1 ]
  1. P0, P1, P2 が一直線上に並んでいる場合、p1 の inhandle 長さは | P0 - P1 | / 3, outhandle 長さは | P2 - P1 | / 3

  2. 4点の正方形を形作る閉じた線形状に smooth をかけた場合、得られる曲線の中点が、4点の外接円上に存在する


まず、正方形に smooth をかけると円になりますが、真円ではありません。

次の [ script 19 - 1 ] で半径 10000 の円形の線形状を作り、bzier parameter t = 0.5, 0.25 の 2点での座標値を求め、その点での半径を調べます。


検証結果

     t = 0.5,     r = 10000.0000838
     t = 0.25,   r = 10002.5869702


[ script 19 - 1 ]
import labo

scene = xshade.scene()

scene.create_disk(None, [0, 0, 0], 10000, 2)
scene.convert_to_line_object()
bz = labo.get_bezier(xshade, 0)

p = labo.bezier_line_position(bz, 0.5)
print 't = 0.5,   r = ', p.abs()
p = labo.bezier_line_position(bz, 0.25)
print 't = 0.25,   r = ', p.abs()

さらに [ script 19 - 2 ] で、外接円半径 10000 の正3角形と正6角形の線形状に smooth をかけて、同様に真円度をチェックします。
**検証結果**

     n = 3    t = 0.5    r = 9307.1990596
     n = 6    t = 0.5    r = 10095.9871577


[ script 19 - 2 ]

import labo
from math import pi, cos, sin
from vec3 import *

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

for n in [3, 6] :
	p = []
	for i in range(n) :
		p.append(10000*vec3(cos(i*2*pi/n), sin(i*2*pi/n), 0))
	
	scene.begin_creating()
	scene.create_line(None, p, True)
	scene.end_creating()
	scene.smooth()

	bz = labo.get_bezier(xshade, 0)
	p = labo.bezier_line_position(bz, 0.5)
	print 'n = ', n, '   t = 0.5   r = ', p.abs()

このように、正N角形に smooth をかけたると次のようになります。

     N = 3:  円形よりも縮む
     N = 4:  中点のみがアンカーポイントと同じ半径を占める
     N > 4:  円形よりも膨らむ


#2

19 - 2  正N角形を円形に


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

**正N角形**に対して smooth をかけたときに円形が得られるような handle 長さの条件を考えてみます。

円形が得られる といっても、あくまでも bezier line の中点がアンカーポイントと同じ半径を占める という条件です。

De Casreljau’s algorythm を用いた handle の図を一般化して図示すると、

     [ fig 19 - 1 ]

          [ 式 19 - 1 ]


よって handle 長さの係数 h は

          [ 式 19 - 2 ]


3点が直線状に並んだ場合は、

          [ 式 19 - 3 ]


となって、[ 条件 17 - 1 ] の第一項も満足します。


なお、Shade original smooth である [ 式 17 - 1 ] での handle 長さの係数 h は P0, P1, P2 のなす角 θ の関数でしたが、[ 式 19 - 2 ] では handle と P1 - P0 あるいは P1 - P2 とのなす角 φ の関数として定義しています。

よって正N角形に対しては handle 長さの微調整になりますが、P0, P1, P2 が左右非対称の場合には長さの変更は大きくなる場合があります。




[ script 18 - 1 ] 内に記述された関数 custom_smooth_handle( ) の handle 長さの係数を求める部分を [ 式 19 - 2 ] に置き換えると次のようになり、正N角形に対して可能な限り真円に近い形状が得られるようになります。


[ script 19 - 3 ]

def custom_smooth_handle(p0, p1, p2, c, d) :
	v0 = p0 - p1
	v2 = p2 - p1
	
	r0 = v0.norm()
	r2 = v2.norm()
	if (r0 == 0) and (r2 == 0) :
		return vec3(p1), vec3(p1)			#  handle なし
	elif min(r0, r2)/max(r0, r2) <=1e-4 :	#  隣接する2点のいずれかが同一座標とみなし、handle を設けない
		return vec3(p1), vec3(p1)

	#  vH : handle の方向
	vH = ((1 - c)*r0 + c*r2)*v2 - (c*r0 + (1 - c)*r2)*v0	
	vH.norm()

	#  h0, h2 : handle 長さの係数 ( 正N角形に smooth を施すと、曲線の中点が正N角形の外接円に接する )
	h0 = 2./3/(1 - v0.dot(vH))	
	h2 = 2./3/(1 +  v2.dot(vH))
	
	inH = p1 - r0*h0*vH
	outH = p1 + r2*h2*vH
	
	#  h : handle 長さの係数
	#	v = v0*v2
	#	a = v.abs()
	#	h = 1./3 + a*(4./3*(1 - 2**0.5/2) - 1./3)
	#	h = h**(1./d)

	#	inH = p1 - (r0*h*vH)
	#	outH = p1 + r2*h*vH

	return inH, outH

[ script 18 - 1 ] 内の関数を [ script 19 - 3 ] に変えた script による smooth を調べると、鈍角のコーナーでは handle が短めに、鋭角ではかなり長めになり、正N角形に対して綺麗な円形が得られる代わりに、全体のバランスが崩れてしまいます。

          [ fig 19 - 2 ]


#3

19 - 3  Handle 方向の制御


関連記事 :

18 Custom Smooth ( 1/3 )


**18 - 2 Custom Smooth** では handle 方向を引数 c でコントロールできるようにしています。

          [ 式 18 - 2 ]


ここで、original のように 丸くする smooth ではなく、どちらかといえば 元形状に忠実な形を維持する ような smooth 形状を目指し、係数 c を次のように制御をしてみます。

          [ 式 19 - 4 ]

     

     [ fig 19 - 3 ]


#4

19 - 4  Handle 長さの制御


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

**18 - 2 Custom Smooth** では handle 長さの係数 h にかかる べき乗の指数 d で handle 長さをコントロールできるようにしています。

          [ 式 18 - 3 ]


handle 長さについても 19 - 3 で定めた handle 方向の制御に応じて、元形状に忠実な形を維持 できるように、h に乗ずる2種類の補正項 f, g を次のように定めます。


補正係数 f0, f2

     P1 で鋭角に折れている場合、左右の辺長の比によって handle 長さを短くする。

          [ 式 19 - 5 ]


**補正係数 g0, g2**

     P1 で鋭角に折れている場合、辺長に係わらず handle 長さを短くする。


ただし、正3角形を円形にする オプションが指定された場合は、90度以下ではなく 60度以下の鋭角に折れている場合に補正が適用され、係数も [ 式 19 - 6 ] 右側の式にします。

この 正3角形を円形にする オプションを適用すると、鋭角部分でやや膨らむ一方、全体のバランスがやや崩れることがあります。


          [ 式 19 - 6 ]


**handle 長さの係数 h0, h2**

     f0 と g0 および f2 と g2 の小なる方を h に乗じたもの

          [ 式 19 - 7 ]


handle を上記のようにコントロールしても **[ 条件 17 - 1 ] の第一項( 1/3 handle )** は満足され、**正N角形に対して真円に近い形状を得る** 条件も N >= 4 で満足され、N = 3 に対してはオプションを指定することで満足されます。

#5

19 - 5  Script


[ script 19 - 4 ] では、以上の custom 機能を組み込んだ関数 **c_smooth( )** を定義し、Shade 上で選択された線形状に対し custom smooth 処理を施します。
[ script 19 - 3 ] で一旦失われた全体のバランスは回復し、original smooth ほど丸くは膨らまない smooth が得られます。
[ script 19 - 4 ]
from vec3 import *
from matrix import *


#  c smooth に与える引数   ######
tri = False			#  3 points circle
straight = False	#  開端部の形状
################################


# c_smooth_handle(p0 as vec3, p1 as vec3, p2 as vec3) as vec3, vec3
#	3 点 p0, p1, p2 の座標から 中央のポイントの inhandle, outhandle 座標を返す 
#	tri = True ならば、鋭角部分でやや膨らみ、正三角形を円形に整形できるが、全体のバランスが崩れることもある

def c_smooth_handle(p0, p1, p2, tri = False) :
	v0 = p0 - p1
	v2 = p2 - p1
	
	r0 = v0.norm()
	r2 = v2.norm()
	if (r0 == 0) and (r2 == 0) :
		return vec3(p1), vec3(p1)			#  handle なし
	elif min(r0, r2)/max(r0, r2) <=1e-4 :	#  隣接する2点のいずれかが同一座標とみなし、handle を設けない
		return vec3(p1), vec3(p1)			#  handle なし

	#  c : handle の方向を定める係数 ( p1 を頂点、p0 - p2 を底辺とした三角形の歪みに応じて handle の方向を定める )
	v1 = p2 - p0
	v1.norm()
	t0 = v1.dot(p1 - p0)
	t2 = v1.dot(p2 - p1)
	if (t0 != 0) or (t2 != 0) :
		c = 1 - abs(min(t0, t2)/max(t0, t2))
	else :
		c = 0.5
	
	#  vH : handle の方向
	vH = ((1 - c)*r0 + c*r2)*v2 - (c*r0 + (1 - c)*r2)*v0	
	vH.norm()
	
	#  h0, h2 : handle 長さの係数 ( 正N角形に smooth を施すと、曲線の中点が正N角形の外接円に接する )
	h0 = 2./3/(1 - v0.dot(vH))	
	h2 = 2./3/(1 +  v2.dot(vH))
	
	#  f0, f2 : 補正係数 1 ( 左右の辺長の比によって handle 長さを短くする )
	f0, f2 = 1, 1

	cosT = v0.dot(v2)
	if cosT > 0.5 :
		if r0 > r2 :	
			f0 = r2/r0
			f2 = f0**0.5
		elif r0 < r2 :
			f2 = r0/r2
			f0 = f2**0.5
	
	#  g0, g2 : 補正係数 2 ( 鋭角をなす頂点で handle 長さを短くする )
	g0, g2 = 1, 1

	if not tri :
		if cosT > 0 :
			v = v0*v2
			a = v.abs()
			g0 = a**2
			g2 = a**2
	else :				
		if cosT > 0.5 :
			v = v0*v2
			a = v.abs()/(3**0.5/2)
			g0 = a**2
			g2 = a**2
		
	
	#  f0, g0, f2, g2 による h0 の補正 ( f, g のうち、小さい方の値を適用 )
	h0 = h0*min(f0, g0)
	h2 = h2*min(f2, g2)	

	
	inH = p1 - r0*h0*vH
	outH = p1 + r2*h2*vH
	
	return inH, outH
	
	
	

#  c_smooth_terminal_handle(p0 as vec3, p1 as vec3, h as vec3, s as bool = False) as vec3, vec3
#	開端部の handle を返す
#	straight = True ならば、直線状の handle

def c_smooth_terminal_handle(p0, p1, h, straight) :
	if not straight :			#  曲線状
		c = (p0 + p1)/2
		v = p1 - p0
		v.norm()
		d = (h - c).dot(v)
		h2 =  h - 2*d*v
		h1 = 2*p0 - h2
		
	else :						#  直線状
		v = h - p0
		v.norm()
		u = h - p1
		d = u.abs()
		h2 =  p0 + d/2*v
		h1 = p0 - d/2*v
	
	return h1, h2
		


	
#  c_smooth(xshade as xshade, straight as bool = False, tri as bool = False)
#	Shade 上で選択されている線形状 / 自由曲面に custum smooth をかける
#	straight = True ならば、開端部を直線状にする
#	tri = True ならば、鋭角部分でやや膨らみ、正三角形を円形に整形できるが、全体のバランスが崩れることもある

def c_smooth(tri = False, straight = False, obj = None) :
	if obj == None :
		ob = scene.active_shape()
	else :
		ob = obj
		
	if not isinstance(ob, xshade.line) :			#   線形状以外なら、
		if isinstance(ob, xshade.part) :
			if ob.part_type == 1 :					#  surfaace part なら、
				if ob.has_son :
					obj = ob.son
					while obj.has_bro :
						obj = obj.bro
						c_smooth(tri, straight, obj)
					return
				else :
					return
			else :
				return
		else :
			return
		
	nop = ob.number_of_control_points				#  point 数
	closed = ob.closed								#  線形状の開閉
	if nop == 1 :									#  point 数が1個のみなら、何もしない
		return
		
	is_modify_mode = scene.is_modify_mode			#  modify mode

	if not is_modify_mode :
		target = [i for i in range(nop)]			#  smooth 処理対象ポイントリスト
		n = nop										#  対象ポイント数
	else :
		n = ob.number_of_active_control_points		#  対象ポイント数
		if n == 0 :									#  modify mode で選択ポイント数が 0 ならば、何もしない
			return
			
		target = ob.active_vertex_indices
		target = list(target)						#  tuple から list に変換
		
	last_p = (not closed) and (target[n - 1] == nop - 1)
	if last_p :										#  開いた線形状で終端が対象なら、target list から終端を削除
		target.pop()
		n -= 1
	
	if n == 0 :
		first_p = False
	else :
		first_p = (not closed) and (target[0] == 0)
		if first_p :								#  開いた線形状で始端が対象なら、target list から始端を削除
			target.remove(0)
	
	mx1 = matrix(ob.local_to_world_matrix)
	mx2 = matrix(ob.world_to_local_matrix)
				
				
	for k in target :
		if closed and k == 0 :
			p0 = vec3(ob.control_point(nop - 1).position)
		else :
			p0 = vec3(ob.control_point(k - 1).position)
			
		p1 = vec3(ob.control_point(k).position)
		
		if closed and k == nop - 1 :
			p2 =vec3(ob.control_point(0).position)
		else :
			p2 =vec3(ob.control_point(k + 1).position)	
				
		mx1.transform([p0, p1, p2])			
		inH, outH = c_smooth_handle(p0, p1, p2, tri)
		mx2.transform([inH, outH])
		ob.control_point(k).in_handle = inH
		ob.control_point(k).out_handle = outH
		ob.control_point(k).linked = True

	if first_p :									 #  開いた線形状で始端が対象なら
		p0 = vec3(ob.control_point(0).position)
		p1 = vec3(ob.control_point(1).position)
		
		if not closed and nop == 2 :
			mx1.transform([p0, p1])
			inH = 4./3*p0 - 1./3*p1
			outH = 2./3*p0 + 1./3*p1
		else :
			h = vec3(ob.control_point(1).in_handle)
			mx1.transform([p0, p1, h])	
			inH, outH = c_smooth_terminal_handle(p0, p1, h, straight)
			
		mx2.transform([inH, outH])
		ob.control_point(0).in_handle = inH
		ob.control_point(0).out_handle = outH
		ob.control_point(0).linked = True
		
	if last_p :										#  開いた線形状で終端が対象なら
		p0 = vec3(ob.control_point(nop - 1).position)
		p1 = vec3(ob.control_point(nop - 2).position)
		
		if not closed and nop == 2 :
			mx1.transform([p0, p1])
			inH = 2./3*p0 + 1./3*p1
			outH = 4./3*p0 - 1./3*p1
		else :
			h = vec3(ob.control_point(nop - 2).out_handle)
			mx1.transform([p0, p1, h])
			outH , inH = c_smooth_terminal_handle(p0, p1, h, straight)
		mx2.transform([inH, outH])
		ob.control_point(nop - 1).in_handle = inH
		ob.control_point(nop - 1).out_handle = outH
		ob.control_point(nop - 1).linked = True
		
		
		
		
scene = xshade.scene()

c_smooth(tri, straight)

**適用例**

          [ fig 19 - 4 ]

          [ fig 19 - 5 ]

          [ fig 19 - 6 ]