Shade3D 公式

17 Smooth [ Shade Labo ]


#1

17 - 1 Shade Smooth


Shade の smooth 処理は次のようになっています。

隣接する3つのポイント P0, P1, P2 から、中央のポイントに発生させる inhandle と outhandle の座標 inH, outH を次のように求めます。

          [ fig 17 - 1 ]

          [ 式 17 - 1 ]


[ 式 17 - 1 ] は次の2つの条件から定められています。


[ 条件 17 - 1 ]

  1. P0, P1, P2 が一直線上に並んでいる場合、p1 の inhandle 長さは | P0 - P1 | / 3, outhandle 長さは | P2 - P1 | / 3

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


#2

17 - 2  1/3 handle


関連記事:
  • 06 Bezier Line 基礎

1/3 handle と呼ばれ、2点間の直線に対して両端から2点間の距離の 1/3 長さの handle を出した直線状の bezier line では、**bezier parameter t が弧長 parmeter s を規格化( 全長を 1 とする )したものに一致する** という性質があります。

つまりこの場合、t = 1/4 で与えられる直線上の座標は、anchor point 間の距離の 1/4 の場所に位置します。


証明

bezier line の4つの control point 座標 [ p0, p1, p2, p3 ] の parameter t での bezier line 上の座標 p( t ) を与える [ 式 06 - 1 ] を書き換えると、

     

     [ 式 17 - 2 ]




[ 式 17 - 1 ] で P0, P1, P2 が一直線上に並んでいる場合、以下のようになり [ 条件 17 - 1 ] の第一項を満足しています。

          [ 式 17 - 3 ]


#3

17 - 3 De Casteljau’s Algorithm


関連記事:
  • 08 Bezier line 等分割
  • 15 Bezier line 分割

bezier line の分割は [ 式 15 - 3 ], [ 式 15 - 9 ] で表されますが、これを幾何的に表したのが De Casteljau’s Algorithm で [ fig 17 - 2 ] に示す簡潔な幾何で表現されます。

08 - 5 bezier parameter による分割 で述べた、ポイント追加時の parameter の比例配分は、これを表しています。


     [ fig 17 - 2 ]


#4

17 - 4  円


De Casreljau’s algorythm を用いて単位半径の 1/4 円 を構成する bezier line の handle 長さ H を求めると、

          [ 式 17 - 4 ]




一方、[ 式 17 - 1 ] より同一条件で handle 長さ H を求めると、

          [ 式 17 - 5 ]




2つの方法で求めた handle 長さは一致しており、[ 条件 17 - 1 ] の第二項を満足しています。


#5

17 - 5  handle 長さの係数


[ 式 17 - 1 ] で与えられる handle 長さの係数 h は [ 条件 17 - 1 ] による2つの要求項目

          [ 式 17 - 6 ]


を parameter a = sin(θ) で結合したものになっています。

           [ 式 17 - 7 ]


#6

17 - 6  重点判定


[ 式 17 - 1 ] に示されるように、smooth 処理では隣接する3点の情報から中央点の handle 情報が決定されます。

この時、隣接3点の距離が下に記す [ 式 17 - 8 ] の条件下にあれば、中央点はいずれかの隣接点と同一座標にあると見なし、handle を発生しません。

また、r0, r2 の距離が他のポイント間の距離に対してどんなに小さくとも [ 式 17 - 8 ] を満足しなければ、中央点には 極小の handle が発生します。

          [ 式 17 - 8 ]




[ script 17 - 1 ], [ script 17 - 2 ], [ script 17 - 3 ] はこの重点判定の検証 script です。


[ script 17 - 1 ]

from vec3 import *
import labo

scene = xshade.scene()

L = 0.3
print ' '
scene.exit_modify_mode()
scene.create_part('test - 1')

for h in [0.9539, 0.954] :
	p0 = vec3(0, 0, 0)
	p1 = vec3(L, h, 0)
	p2 = vec3(10000, 0, 0)

	labo.make_simple_line(xshade, [p0, p1, p2], False)
	scene.smooth()

	v1 = p1 - p0
	v2 = p2 - p1
	r1 = v1.abs2()
	r2 = v2.abs2()
	ratio = (r1/r2)**0.5
	print 'ratio = ', ratio
	
scene.select_parent(1)

[ script 17 - 2 ]
from vec3 import *
import labo

scene = xshade.scene()

L = 0.3
print ' '
scene.exit_modify_mode()
scene.create_part('test - 2')

for h in [0.72216, 0.72217] :
	p0 = vec3(0, 0, 0)
	p1 = vec3(L, h, 0)
	p2 = vec3(10000, 0, 0)

	labo.make_simple_line(xshade, [p0, p1, p2], False)
	scale = 10000
	scene.move_object([0, 0, 0], None, None, [0.8*scale, 1.4*scale, -1*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)

	ob = scene.active_shape()
	p0 = vec3(ob.control_point(0).position)
	p1 = vec3(ob.control_point(1).position)
	p2 = vec3(ob.control_point(2).position)
	
	scene.smooth()

	v1 = p1 - p0
	v2 = p2 - p1
	r1 = v1.abs2()
	r2 = v2.abs2()
	ratio = (r1/r2)**0.5
	print 'ratio = ', ratio
	
scene.select_parent(1)

[ script 17 - 3 ]
import labo
from vec3 import *


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

L = 1
p0 = vec3(-10000, 0, 0)
p1 = vec3(0, 0, 0)
p2 = vec3(L, 0, 0)
p3 = vec3(L*2, 0, 0)

labo.make_simple_line(xshade, [p0, p1, p2, p3], False)
scene.smooth()
ob = scene.active_shape()

print ''
print 'point1'
print 'inhandle : ', ob.control_point(1).has_in_handle
print 'othandle : ', ob.control_point(1).has_out_handle
print ''
print 'point2'
print 'inhandle : ', ob.control_point(2).has_in_handle
print 'othandle : ', ob.control_point(2).has_out_handle

h1 = vec3(ob.control_point(2).in_handle)
h2 = vec3(ob.control_point(2).out_handle)
v1 = h1 - p2
v2 = h2 - p2 
print 'inhandle length : ', v1.abs()
print 'outhandle length : ', v2.abs()

#7

17 - 7  開端部


開いた線形状の端部での計算では、隣接するポイント handle の mirror handle を設けます

          [ 式 17 - 9 ]


#8

17 - 8  smooth script


以上より、Shade の smooth 処理をシミュレートする script を2つ作り、labo に登録してあります。
**smooth( )**
  • Shade 上で選択されている 線形状 / 自由曲面 に smooth をかけます。( xshade.scene().smooth() と同じ )

  • modify mode 時、Shade の smooth では 選択ポイントに handle が設けられている場合には、inhandle と outhandle の長さを揃える処理がなされますが、この script ではそれはサポートしません。

  • modify mode では handle の有無に関係なく、選択ポイントに対してスムース処理を施します。


**smooth_line( )**
  • script 内部で線形状データに smooth をかけます。

  • Shade に線形状を出力することなく、smooth のかかった線形状データが script 内で得られます。


[ script 17 - 4 ]

     2016/02/13 smooth_handle() 修正   3重点をフォロー

#  smooth_handle(p0 as vec3, p1 as vec3, p2 as vec3) as vec3, vec3
#	3 点 p0, p1, p2 の座標から 中央のポイントの inhandle, outhandle 座標を返す 

def smooth_handle(p0, p1, p2) :
	v0 = p0 - p1
	v2 = p2 - p1
	
	r0 = v0.norm()
	r2 = v2.norm()
	if (r0 == 0) and (r2 == 0) :			#  2015/02/13 追加
		return vec3(p1), vec3(p1)			#  handle なし
	elif min(r0, r2)/max(r0, r2) <= 1e-4 :	#  隣接する2点のいずれかがくっついているとみなす
		return vec3(p1), vec3(p1)			#  handle なし
	
	vH = r0*v2 - r2*v0
	vH.norm()								#  handle の方向
	
	v = v0*v2
	a = v.abs()
	h = 1./3 + a*(4./3*(1 - 2**0.5/2) - 1./3)
	
	inH = p1 - (r0*h*vH)
	outH = p1 + r2*h*vH
	
	return inH, outH
	
	
	

#  mirror_handle(p0 as vec3, p1 as vec3, h as vec3) as vec3, vec3
#	 開端部の mirro handle を返す

def mirror_handle(p0, p1, h) :
	c = (p0 + p1)/2
	v = p1 - p0
	v.norm()
	d = (h - c).dot(v)
	h2 =  h - 2*d*v
	h1 = 2*p0 - h2
	
	return h1, h2
		


	
#  smooth(xshade as xshade)
#	Shade 上で選択されている線形状 / 自由曲面に smooth をかける
#	modify mode であれば、選択ポイントにのみ smooth をかける

def smooth(xshade, obj = None) :
	scene = xshade.scene()
	
	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
						smooth(xshade, 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 = smooth_handle(p0, p1, p2)
		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 = mirror_handle(p0, p1, h)
			
		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 = mirror_handle(p0, p1, h)
		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
		
	
	

#  smooth_line(pL as vec3 list, inH as vec3 list, outH as vec3 list, closed as bool, target as int list = None)
#
#	点列 pL に smooth を施し、inH, outH に handle 座標を格納する
#	closed : 線形状の開閉情報
#	default では全てのポイントが smooth 対象だが、target list を指定すれば、
#	そこに格納されているポイント番号に対してのみ処理を行う

def smooth_line(pL, inH, outH, closed, target = None) :	 
	if pL == None :
		return
	
	nop = len(pL)
	if nop <= 1 :
		return
	else :
		for p in pL :
			if p == None :
				print 'pL の中に None が入っています。  smooth_line( )'
				return
		
	if target != None :
		if len(target) == 0 :
			return
		target = list(target)
	else :
		target = [i for i in range(nop)]
		
	n = len(inH)
	if n < nop :
		for i in range(n, nop) :
			inH.append(None)
			
	n = len(outH)
	if n < nop :
		for i in range(n, nop) :
			outH.append(None)
	
	n = len(target)
	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)
		
	
	for k in target :
		if closed and k == 0 :
			p0 = pL[nop - 1]
		else :
			p0 = pL[k - 1]
			
		p1 = pL[k]
		
		if closed and k == nop - 1 :
			p2 =pL[0]
		else :
			p2 =pL[k + 1]	
				
		in_h, out_h = smooth_handle(p0, p1, p2)
		inH[k] = in_h
		outH[k] = out_h

		
	if first_p :									#  開いた線形状で始端が対象なら
		p0 = pL[0]
		p1 = pL[1]
		
		if not closed and nop == 2 :
			in_h = 4./3*p0 - 1./3*p1
			out_h = 2./3*p0 + 1./3*p1
		else :
			h = inH[1]
			if h != None :
				in_h, out_h = mirror_handle(p0, p1, h)
			else :
				in_h = vec3(pL[0])
				out_h = vec3(pL[0])
				
		inH[0] = in_h
		outH[0] = out_h
		
		
	if last_p :										#  開いた線形状で終端が対象なら
		p0 = pL[nop - 1]
		p1 = pL[nop - 2]
		
		if not closed and nop == 2 :
			in_h = 2./3*p0 + 1./3*p1
			out_h = 4./3*p0 - 1./3*p1
		else :
			h = outH[nop - 2]
			if h != None :
				out_h , in_h = mirror_handle(p0, p1, h)
			else :
				in_h = vec3(pL[nop - 1])
				out_h = vec3(pL[nop - 1])
				
		inH[nop - 1] = in_h
		outH[nop - 1] = out_h

次の script は同一の折れ線に対し Shade の original smooth と、[ script 17 - 4 ] で与えた labo.smooth( ), labo.smooth_line( ) とで smooth をかけています。

3ケースとも同じ結果を与えます。


[ script 17 - 5 ]

import labo
from vec3 import *


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

p = []
p.append(vec3(-12000, -7500, 0))
p.append(vec3(-15000, 5000, 0))
p.append(vec3(-4000, 12000, 0))
p.append(vec3(-2500, -8500, 0))
p.append(vec3(8000, -2000, 0))
p.append(vec3(1000, 1000, 0))
p.append( vec3(10000, 10000, 0))
p.append(vec3(11000, -10000, 0))

scene.create_part('smooth')

labo.make_simple_line(xshade, p, False, 'original smooth')
scene.smooth()

labo.make_simple_line(xshade, p, False, 'simulated smooth')
labo.smooth(xshade)

inH = []
outH = []
labo.smooth_line(p, inH, outH, False)
labo.make_line(xshade, p, inH, outH, False, 'smoothed line')

scene.select_parent(1)