Shade3D 公式

20 Custom Smooth( 3 / 3 ) [ Shade Labo ]


#1

20 - 1  S Curve


2016 / 02 / 28   修正
  • [ script 20 - 3 ] の 314 行に余分な文字が挿入されていたのを削除

2016 / 03 / 07
  • 20 - 4 補遺 - local 座標系    追加


二次元平面上での bezier line の handle の向きと曲線形状の関係を調べます。

一方の handle を固定してもう一方を時計回りに回転すると下図のように変化します。

          [ fig 20 - 1 ]


E ~ H では曲線は S curve を描き、他では C curveとなります。


smooth 処理でも S curve は発生します。

          [ fig 20 - 2 ]


上図右の場合、一方の handle 長さを短く調整すれば S curve を C curve に変形できます。

          [ fig 20 - 3 ]


#2

20 - 2  S Curve 補正


S curve を C curve に変形する作業は次のように記述されます。
  • 一組の相対する handle の一方の handle の延長線をとったとき、他方の handle に交差するなら、その区間の曲線は S curve を描いている( これは S curve の十分条件であって、必要条件ではありません )

  • この時、交差される handle の長さを交点まで短くすれば、S curve は解消され、C curve になる。


              [ fig 20 - 4 ]



この曲線を滑らかにする 補正作業 は三次元曲線に対しても有効です。

三次元の場合、一組の handle の方向ベクトル V1, V2 で定義される面法線 Vn なる平面に投影して得られる二次元曲線で考えます。

結局、三次元曲線では handle 延長線の交点の代わりに、handle 延長線の最接近点 Q1, Q2 を基準にする ことになります。


          [ fig 20 - 5 ]


[ script 20 - 1 ] は [ fig 20 - 5 ] を再現する script です。


[ script 20 - 1 ]

import labo
from vec3 import *
from quaternion import *


scene = xshade.scene()
scene.create_part()

#  test line
bz = []
bz.append(vec3(-8000, 10000, -3500))
bz.append(vec3(-4000, 13000, 3000))
bz.append(vec3(-3000, 7000, 10000))
bz.append(vec3(5000, 10000, 7500))

scene.begin_creating()
scene.begin_line('test line', False)
scene.append_point(bz[0], None, bz[1], None, None)
scene.append_point(bz[3], bz[2], None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()

#  vn : outhandle と inhandle で定義される平面
vn = (bz[2] - bz[3])*(bz[1] - bz[0])
vn.norm()

#  原点を通り面法線 vn なる面
rect = [vec3(10000, 0, 10000), vec3(10000, 0, -10000), vec3(-10000, 0, -10000), vec3(-10000, 0, 10000)]
Q = quaternion().slerp(vec3(0, 1, 0), vn)
Q.transform(rect)
scene.begin_creating()
scene.create_line('plane',rect,True)
scene.end_creating()


#  test line を原点を通り面法線 vn なる面に投影
bz2 = []
for i in range(4) :
	bz2.append(bz[i] - vn.dot(bz[i])*vn)

scene.begin_creating()
scene.begin_line('projected line', False)
scene.append_point(bz2[0], None, bz2[1], None, None)
scene.append_point(bz2[3], bz2[2], None, None, None)
scene.end_line()
scene.end_creating()
ob2 = scene.active_shape()

#  ロクロ回転
scene.scroll([0, 0, 0])                                 
Q = quaternion().slerp(vn, vec3(0, 1, 0))
scene.lathe_rotation = list(Q.conj())
scene.active_shapes = [ob1, ob2]
scene.enter_modify_mode()
scene.select_all()   

一組の相対する point を **P1**, **P2** その handle 方向を **V1**, **V2**( 単位ベクトル )とすれば、handle を延長した二直線の最接近点 **Q1**, **Q2** は次の [ 式 20 - 1 ] で与えられます。

なお、2つの handle が同一平面内に存在する場合には、次式で得られる最接近点は交点に一致します。


     [ 式 20 - 1 ]


#3

20 - 3  Script


2016 / 02 / 28
  • [ script 20 - 3 ] の 314 行に余分な文字が挿入されていたのを削除


3つの script を作ります。
[ script 20 - 2 ]
  • S curve 補正の効果検証のための test 線形状を出力します。

[ script 20 - 3 ]     smooth_x( )

  • 複数のオブジェクトに対するバッチ処理をサポート
  • Shade の original smooth に S curve 補正 を追加し、オプションとして 開端部を直線にする を追加
  • modify mode 時に選択ポイントに handle が出ていれば、handle 長さを揃えるのではなく、S curve 補正を施す

[ script 20 - 4 ]     c_smooth( )

  • 複数のオブジェクトに対するバッチ処理をサポート
  • [ script 19 - 4 ] の smooth に、S curve 補正を追加( 3 points circle と 開端部を直線的にする オプション付き )
  • modify mode 時は、選択ポイントの handle の有無に関わりなく smooth 処理を行う

下図は [ script 20 - 2 ] で出力された線形状に Shade smooth, [ script 20 - 3 ], [ script 19 - 4 ], [ script 20 - 4 ] を適用して比較したものです。

          [ fig 20 - 6 ]

          [ fig 20 - 7 ]




[ script 20 - 2 ]

import labo

p = []
p.append([-7000, 0, -3000])
p.append([-3500, -1500, -11000])
p.append([5500, 0, -12000])
p.append([11000, 1000, -10500])
p.append([14000, 0,-8500])
p.append([13000, -1500, 3000])
p.append([3000, 0,10000])
p.append([-5500, 1500, 12500])
p.append([-10000, 2500, 10500])
p.append([-11500, 2500, 6000])
p.append([3000, 1500, 3500])
p.append([500, 0, -5000])

labo.make_simple_line(xshade, p, False, 's curve')

[ script 20 - 3 ]     **smooth_x( )**
     2016 / 02 / 28

     * 314 行に余分な文字が挿入されていたのを削除


from vec3 import *
from matrix import *


#  smooth _x   #####################
straight = False		#  開端部の形状
####################################


#  smooth_handle_x(p0 as vec3, p1 as vec3, p2 as vec3) as vec3, float, vec3, float
#	3 点 p0, p1, p2 の座標から 中央のポイントの inhandle, outhandle の方向 vector と長さを返す 

def smooth_handle_x(p0, p1, p2) :
	v0 = p0 - p1
	v2 = p2 - p1
	
	r0 = v0.norm()
	r2 = v2.norm()
	if (r0 == 0) and (r2 == 0) :
		return vec3(), 0, vec3(), 0			#  inhandle vector, inhandle lenght, outhandle vector, outhandle lenght		handle なし
	elif min(r0, r2)/max(r0, r2) <=1e-4 :	#  隣接する2点のいずれかがくっついているとみなす
		return vec3(), 0, vec3(), 0			#  inhandle vector, inhandle lenght, outhandle vector, outhandle lenght		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)
	
	return -vH, r0*h, vH, r2*h				#  inhandle vector, inhandle lenght, outhandle vector, outhandle lenght
	
	

#  smooth_terminal_handle(p0 as vec3, p1 as vec3, h as vec3, s as bool = False) as vec3, float, vec3, float
#	開端部の  inhandle, outhandle の方向 vector と長さを返す 
#	straight = True ならば、直線状の handle

def 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
		v = h2 - p0
		r = v.norm()
		
	else :							#  直線状
		v = h - p0
		v.norm()
		u = h - p1
		r = u.abs()/2

	return -v, r, v, r
		


#  correct_smooth_handle(p1 as vec3, v1 as vec3, r as list, k as int, p2 as vec3, v2 as vec3)
#	s curve check を行い、handle 長さ r[k] を調整
		
def correct_smooth_handle(p1, v1, r, k, p2, v2) :
	if v1.abs2() == 0 or v2.abs2() == 0 :
		return
	
	s1 = v1.dot(p2 - p1)
	s2 = -v2.dot(p2 - p1)
	s = v1.dot(v2)
	if abs(s) != 1 :
		t = (s2*s + s1)/(1 - s**2)
		if (t > 0) and (t < r[k]) :		#   最接近点が handle 1 の途中にある
			r[k] = t
			
	

	
#  smooth_x(straight as bool = False)
#	Shade 上で選択されている線形状 / 自由曲面に smooth をかける ( 複数選択可 )
#	Shade original smooth に S curve 補正を追加
#	modify mode 時は、選択ポイントに対して S curve 補正のみを行う
#	straight = True ならば、開端部を直線状にする

def smooth_x(straight = False) :
	obL = scene.active_shapes						#  複数選択に対するバッチ処理対応							
	for ob in obL :
		if isinstance(ob, xshade.line) :			#   線形状なら、
			smooth_x_(straight, ob)
			
		elif 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_x_(straight, obj)
						

def smooth_x_(straight, ob) :	
	#  nop, closed, is_modify_mode	
	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

	
	#  target
	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 に変換

				
	#  mx1, mx2, p, inv, outV, inL, outL
	mx1 = matrix(ob.local_to_world_matrix)
	mx2 = matrix(ob.world_to_local_matrix)
				
	p = [None for i in range(nop)]					#  anchor point 座標を格納する list
	inV = [None for i in range(nop)]				#  inhandle の方向 vector を格納する list
	outV = [None for i in range(nop)]				#  outhandle の方向 vector を格納する list
	inL = [None for i in range(nop)]				#  inhandle の長さを格納する list
	outL = [None for i in range(nop)]				#  outhandle の長さを格納する list

				
	#  modify mode の前処理
	if is_modify_mode :	
		target2 = []								#  smooth 処理対象 pooint list
		target3 = []								#  S curve 補正のみが適用される point list
		linked = []									#  current handle link 情報
		for k in target :
			#  どちらかの handle が存在する ( smooth は行わず S cirve 補正のみ )
			#  ポイント関連の座標データ取得
			if ob.control_point(k).has_in_handle or ob.control_point(k).has_out_handle :
				p_ = vec3(ob.control_point(k).position)
				inV_ = vec3(ob.control_point(k).in_handle)
				outV_ = vec3(ob.control_point(k).out_handle)
				mx1.transform([p_, inV_, outV_])
			
				p[k] = p_
				v = inV_ - p_
				r = v.norm()
				inV[k] = v
				inL[k] = r
				v = outV_ - p_
				r = v.norm()
				outV[k] = v
				outL[k] = r
				target3.append(k)					#  S curve 補正のみが適用される point list
				linked.append(ob.control_point(k).linked)
			else :
				target2.append(k)					#  smooth 処理対象ポイントリスト
				linked.append(True)
				
					
	# smooth 処理のために target の変更
	last_p = (not closed) and (target[n - 1] == nop - 1)
	if last_p :										#  開いた線形状で終端が対象なら、target list から終端を削除
		if not is_modify_mode :
			target.pop()
		else :
			if target2 != [] and (target2[len(target2) - 1] == nop - 1) :
				target2.pop()
			else :
				target3.pop()
				
	first_p = (not closed) and (target != [] and target[0] == 0)
	if first_p :									#  開いた線形状で始端が対象なら、target list から始端を削除
		if not is_modify_mode :
			target.remove(0)
		else :
			if target2 != [] and (target2[0] == 0) :
				del target2[0]
			else :
				del target3[0]

					
	#  smooth	
	if is_modify_mode :
		target = target2

	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])	
		
		p[k] = p1
		inV[k], inL[k], outV[k], outL[k] = smooth_handle_x(p0, p1, p2)

	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])
			v = p1 - p0
			r = v.norm()
			p[0] = p0
			inV[0] = -v
			inL[0] = r/3
			outV[0] = v
			outL[0] = r/3
			
		else :
			if p[1] != None :
				h = p[1] + inL[1]*inV[1]
				mx1.transform([p0, p1])
			else :
				h = vec3(ob.control_point(1).in_handle)
				mx1.transform([p0, p1, h])	
			p[0] = p0
			inV[0], inL[0], outV[0], outL[0] = smooth_terminal_handle(p0, p1, h, straight)
		
	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])
			v = p1 - p0
			r = v.norm()
			p[nop - 1] = p0
			inV[nop - 1] = v
			inL[nop - 1] = r/3
			outV[nop - 1] = -v
			outL[nop - 1] = r/3
			
		else :
			if p[nop - 2] != None :
				h = p[nop - 2] + outL[nop - 2]*outV[nop - 2]
				mx1.transform([p0, p1])
			else :
				h = vec3(ob.control_point(nop - 2).out_handle)
				mx1.transform([p0, p1, h])	
			p[nop - 1] = p0
			outV[nop - 1], outL[nop - 1], inV[nop - 1], inL[nop - 1] = smooth_terminal_handle(p0, p1, h, straight)
		
		
	# S curve 補正のために target の変更
	if is_modify_mode :
		target = target + target3
		target.sort()
	
	if closed and target[0] == 0 :						#  閉じていて始端が対象ポイントであれば、target list から外す
		del target[0]
		first_p = True
		
	if closed and target[len(target) - 1] == nop - 1 :	#  閉じていて終端が対象ポイントであれば、target list から外す
		target.pop()
		last_p = True
		
		
	#  S curve 補正
	if first_p :										#  始端が対象ポイント	
		if p[1] != None :
			correct_smooth_handle(p[0], outV[0], outL, 0, p[1], inV[1])
			correct_smooth_handle(p[1], inV[1], inL, 1, p[0], outV[0])
		else :
			p1 = vec3(ob.control_point(1).position)
			p2 = vec3(ob.control_point(1).in_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1	
			r1 = v1.norm()
			correct_smooth_handle(p[0], outV[0], outL, 0, p1, v1)
		
		if not closed and not straight :				#  開いていて、開端部を曲線的にするなら
			inL[0] = outL[0]
		
	for k in target :
		if p[k - 1] == None :
			p1 = vec3(ob.control_point(k - 1).position)
			p2 = vec3(ob.control_point(k - 1).out_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1	
			r1 = v1.norm()
			correct_smooth_handle(p[k], inV[k], inL, k, p1, v1)
		
		if p[k + 1] != None :
			correct_smooth_handle(p[k], outV[k], outL, k, p[k + 1], inV[k + 1])
			correct_smooth_handle(p[k + 1], inV[k + 1], inL, k + 1, p[k], outV[k])
		else :
			p1 = vec3(ob.control_point(k + 1).position)
			p2 = vec3(ob.control_point(k + 1).in_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1
			r1 = v1.norm()
			correct_smooth_handle(p[k], outV[k], outL, k, p1, v1)
		
	if last_p :									#  終端が対象ポイント	
		if p[nop - 2] == None :
			p1 = vec3(ob.control_point(nop - 2).position)
			p2 = vec3(ob.control_point(nop - 2).out_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1	
			r1 = v1.norm()
			correct_smooth_handle(p[nop - 1], inV[nop - 1], inL, nop - 1, p1, v1)
		
		if not closed and not straight : 		#  開いていて、開端部を曲線的にするなら
			outL[nop - 1] = inL[nop - 1]
		
		if closed :								#  閉じていれば
			if p[0] != None :
				correct_smooth_handle(p[nop - 1], outV[nop - 1], outL, nop - 1, p[0], inV[0])
				correct_smooth_handle(p[0], inV[0], inL, 0, p[nop - 1], outV[nop - 1])
			else :
				p1 = vec3(ob.control_point(0).position)
				p2 = vec3(ob.control_point(0).in_handle)
				mx1.transform([p1, p2])
				v1 = p2 - p1
				r1 = v1.norm()
				correct_smooth_handle(p[nop - 1], outV[nop - 1], outL, nop - 1, p1, v1)
			
		
	#  set handle
	if first_p :
		target.insert(0, 0)
	if last_p :
		target.append(nop - 1)
	if is_modify_mode :
		linked.reverse()

	for k in target :
		inH = p[k] + inL[k]*inV[k]
		outH = p[k] + outL[k]*outV[k]
		mx2.transform([inH, outH])
			
		ob.control_point(k).in_handle = inH
		ob.control_point(k).out_handle = outH
		
		if not is_modify_mode :
			ob.control_point(k).linked = True
		else :
			ob.control_point(k).linked = linked.pop()

		
				
scene = xshade.scene()

smooth_x(straight)

[ script 20 - 4 ]     **c_smooth( )**
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, float, vec3, float
#	3 点 p0, p1, p2 の座標から 中央のポイントの inhandle, outhandle の方向 vector と長さを返す 
#	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(), 0, vec3(), 0			#  inhandle vector, inhandle lenght, outhandle vector, outhandle lenght		handle なし

	elif min(r0, r2)/max(r0, r2) <=1e-4 :	#  隣接する2点のいずれかが同一座標とみなし、handle を設けない
		return vec3(), 0, vec3(), 0			#  inhandle vector, inhandle lenght, outhandle vector, outhandle lenght		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 :							#  正3角形を円形にする目的以外ではtri = True としない方がよい				
		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)	

	return -vH, r0*h0, vH, r2*h2	#  inhandle vector, inhandle lenght, outhandle vector, outhandle lenght
	

	
	

#  c_smooth_terminal_handle(p0 as vec3, p1 as vec3, h as vec3, s as bool = False) as vec3, float, vec3, float
#	開端部の  inhandle, outhandle の方向 vector と長さを返す 
#	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
		v = h2 - p0
		r = v.norm()
		
	else :						#  直線状
		v = h - p0
		v.norm()
		u = h - p1
		r = u.abs()/2

	return -v, r, v, r
		


#  correct_smooth_handle(p1 as vec3, v1 as vec3, r as list, k as int, p2 as vec3, v2 as vec3)
#	s curve check を行い、handle 長さ r[k] を調整
		
def correct_smooth_handle(p1, v1, r, k, p2, v2) :
	if v1.abs2() == 0 or v2.abs2() == 0 :
		return
		
	s1 = v1.dot(p2 - p1)
	s2 = -v2.dot(p2 - p1)
	s = v1.dot(v2)
	if abs(s) != 1 :
		t = (s2*s + s1)/(1 - s**2)
		if (t > 0) and (t < r[k]) :		#   最接近点が handle 1 の途中にある
			r[k] = t
			
	

	
#  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) :
	obL = scene.active_shapes						#  複数選択に対するバッチ処理対応							
	for ob in obL :
		if isinstance(ob, xshade.line) :			#   線形状なら、
			c_smooth_(tri, straight, ob)
			
		elif 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)


def c_smooth_(tri, straight, ob) :	
	#  nop, closed, is_modify_mode		
	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

	
	#  target
	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 に変換
		
			
	#  mx1, mx2, p, inv, outV, inL, outL	
	mx1 = matrix(ob.local_to_world_matrix)
	mx2 = matrix(ob.world_to_local_matrix)
				
	p = [None for i in range(nop)]					#  anchor point 座標を格納する list
	inV = [None for i in range(nop)]				#  inhandle の方向 vector を格納する list
	outV = [None for i in range(nop)]				#  outhandle の方向 vector を格納する list
	inL = [None for i in range(nop)]				#  inhandle の長さを格納する list
	outL = [None for i in range(nop)]				#  outhandle の長さを格納する list
			
		
	# smooth 処理のために target の変更
	last_p = (not closed) and (target[n - 1] == nop - 1)
	if last_p :										#  開いた線形状で終端が対象なら、target list から終端を削除
		target.pop()

	first_p = (not closed) and (target != [] and target[0] == 0)
	if first_p :									#  開いた線形状で始端が対象なら、target list から始端を削除
		del target[0]

		
	#  smooth		
	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])	
		
		p[k] = p1
		inV[k], inL[k], outV[k], outL[k] = c_smooth_handle(p0, p1, p2, tri)	

	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])
			v = p1 - p0
			r = v.norm()
			p[0] = p0
			inV[0] = -v
			inL[0] = r/3
			outV[0] = v
			outL[0] = r/3
			
		else :
			if p[1] != None :
				h = p[1] + inL[1]*inV[1]
				mx1.transform([p0, p1])
			else :
				h = vec3(ob.control_point(1).in_handle)
				mx1.transform([p0, p1, h])	
			p[0] = p0
			inV[0], inL[0], outV[0], outL[0] = c_smooth_terminal_handle(p0, p1, h, straight)
		
	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])
			v = p1 - p0
			r = v.norm()
			p[nop - 1] = p0
			inV[nop - 1] = v
			inL[nop - 1] = r/3
			outV[nop - 1] = -v
			outL[nop - 1] = r/3
			
		else :
			if p[nop - 2] != None :
				h = p[nop - 2] + outL[nop - 2]*outV[nop - 2]
				mx1.transform([p0, p1])
			else :
				h = vec3(ob.control_point(nop - 2).out_handle)
				mx1.transform([p0, p1, h])	
			p[nop - 1] = p0
			outV[nop - 1], outL[nop - 1], inV[nop - 1], inL[nop - 1] = c_smooth_terminal_handle(p0, p1, h, straight)
		
	

	# S curve 補正のために target の変更
	if closed and target[0] == 0 :						#  閉じていて始端が対象ポイントであれば、target list から外す
		del target[0]
		first_p = True
		
	if closed and target[len(target) - 1] == nop - 1 :	#  閉じていて終端が対象ポイントであれば、target list から外す
		target.pop()
		last_p = True
	
	
	#  S curve 補正
	if first_p :										#  始端が対象ポイント	
		if p[1] != None :
			correct_smooth_handle(p[0], outV[0], outL, 0, p[1], inV[1])
			correct_smooth_handle(p[1], inV[1], inL, 1, p[0], outV[0])
		else :
			p1 = vec3(ob.control_point(1).position)
			p2 = vec3(ob.control_point(1).in_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1	
			r1 = v1.norm()
			correct_smooth_handle(p[0], outV[0], outL, 0, p1, v1)
		
		if not closed and not straight :				#  開いていて、開端部を曲線的にするなら
			inL[0] = outL[0]
		
	for k in target :
		if p[k - 1] == None :
			p1 = vec3(ob.control_point(k - 1).position)
			p2 = vec3(ob.control_point(k - 1).out_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1	
			r1 = v1.norm()
			correct_smooth_handle(p[k], inV[k], inL, k, p1, v1)
		
		if p[k + 1] != None :
			correct_smooth_handle(p[k], outV[k], outL, k, p[k + 1], inV[k + 1])
			correct_smooth_handle(p[k + 1], inV[k + 1], inL, k + 1, p[k], outV[k])
		else :
			p1 = vec3(ob.control_point(k + 1).position)
			p2 = vec3(ob.control_point(k + 1).in_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1
			r1 = v1.norm()
			correct_smooth_handle(p[k], outV[k], outL, k, p1, v1)
		
	if last_p :									#  終端が対象ポイント	
		if p[nop - 2] == None :
			p1 = vec3(ob.control_point(nop - 2).position)
			p2 = vec3(ob.control_point(nop - 2).out_handle)
			mx1.transform([p1, p2])
			v1 = p2 - p1	
			r1 = v1.norm()
			correct_smooth_handle(p[nop - 1], inV[nop - 1], inL, nop - 1, p1, v1)
		
		if not closed and not straight :		#  開いていて、開端部を曲線的にするなら
			outL[nop - 1] = inL[nop - 1]
		
		if closed :								#  閉じていれば
			if p[0] != None :
				correct_smooth_handle(p[nop - 1], outV[nop - 1], outL, nop - 1, p[0], inV[0])
				correct_smooth_handle(p[0], inV[0], inL, 0, p[nop - 1], outV[nop - 1])
			else :
				p1 = vec3(ob.control_point(0).position)
				p2 = vec3(ob.control_point(0).in_handle)
				mx1.transform([p1, p2])
				v1 = p2 - p1
				r1 = v1.norm()
				correct_smooth_handle(p[nop - 1], outV[nop - 1], outL, nop - 1, p1, v1)
			
		
	#  set handle
	if first_p :
		target.insert(0, 0)
	if last_p :
		target.append(nop - 1)
		
	for k in target :
		inH = p[k] + inL[k]*inV[k]
		outH = p[k] + outL[k]*outV[k]
		mx2.transform([inH, outH])
		ob.control_point(k).in_handle = inH
		ob.control_point(k).out_handle = outH
		ob.control_point(k).linked = True
		
scene = xshade.scene()

c_smooth(tri, straight)

#4

20 - 4  補遺 - local 座標系


2016 / 03 / 07 追加
Shade の用語の中で似て非なるものの代表が **local 座標**と **local 座標系** です。
  • local 座標 に対するのは world 座標

  • local 座標系 に対するのは global 座標系


以下は **local 座標系** の方の話です。
          [ fig 20 - 8 ]
Shade labo での前提条件として述べるのを忘れていましたが、Shade Labo では **形状が変形してしまう非等方スケールの入った local 座標系は想定しない** ことにしています。

smooth_x( )c_smooth( ) も等方スケールの local 座標系 ならば正常に機能しますが、非等方スケールでは不正な結果を与えます。

一方 Shade の original smooth はこれをサポートしており、local 座標系への変換 matrix が一つ余分に挿入されているのだと思われます。

以下に各段階での変換 matrix のかかり方をまとめます。


local 座標

  • Script が返す座標 P は local 座標です

**world 座標**
  • ブラウザ上で上位 matrix による変換後の座標 Pw

  • local 座標 P に対して次のようになる

          Pw = P x ( xshade.scene().active_shape().local_to_world_matrix )


**local matrix**
  • xshade.scene().local_matrix で得られる matrix で、global 座標系から local 座標系への変換を与える

  • ロクロ回転も local 座標として扱われており、global 座標系でのロクロ回転でも発生する

  • local 座標系でロクロ回転を行うと、local 座標系への基準変換 matrix とロクロ回転による matrix との積になる

  • ロクロ回転を含めた global / local 座標系 での座標 QL は world 座標 Pw に対して次のようになる

          QL = Pw x ( local matrix )


**local 座標系への基準変換 matrix**
  • ロクロ回転を含まない local matrix への変換を与える

  • script にはこれを得る機能は実装されてなく、以下の ML として取得可

          ob = xshade.scene().local          # local 座標系の基準となるパート
          Mt = matrix(ob.transformation_matrix)
          ML = Mt.inverse()

  • よって、ロクロ回転を含まない local 座標系での座標 Q は world 座標 Pw に対して次のようになる

          Q = Pw x ML


**smooth_x( )** も **c_smooth( )** も内部の計算では world 座標で行っていますが、非等方スケールの入った local 座標系をサポートするには、この local 座標系での座標 **Q** を用いて計算することになります。

あるいは、余計な変換が加わってしまうことにはなりますが、ロクロ回転を含めた local 座標 QL で計算しても構いません。