Shade3D 公式

23 掃引(2/3)[ Shade Labo ]


#1

関連記事
  • 22 掃引(1/3)

23 - 1   交差線形状ハンドル長さ


**22 掃引(1/3)** で、掃引中心線の各 anchor point 上に断面形状を配置するところまで述べました。

ここではこれらを内蔵する 自由曲面 = 掃引体 の交差方向線形状の handle 長さの求め方について述べます。


結論からいうと、Shade による handle 長さの求め方のロジックは解明できていません。

そこで一部のデータを実測して内蔵データとして Table 化し、そこから線形補間で補正値を得るという方法を用いて、Shade 掃引をかなりの精度で再現できるようにしています。

この handle 長さの求め方のロジックについては、Custom 掃引 で合理的なロジックを提案します。


#2

23 - 2  ハンドル長さの基準平面


以下、**handle 長さ** / **handle 座標** とは、掃引体交差方向線形状のものを示すこととします。
掃引中心線の一つの bezier 区間の 4つの bezier control point を **p0**, **p1**, **p2**, **p3** とします。

中心線の outhandle 座標を p1 を固定してみると、掃引体の outhandle 座標は p0p3 の内、3つの control point 座標のなす角度によって決定されています。

  • outhandle 座標 : ∠ p0, p1, p2

inhadle 側も同様で、やはり 3つの control point 座標のなす角度によって決定されます。
  • inhandle 座標 : ∠ p1, p2, p3

このように handle 長さは 3つの control point を元にした平面上で考察すればよいことになります。

また、handle 先端は一直線上( 同一平面上 )に存在します


[ script 23 - 1], [script 23 - 2 ] は、この検証 script です。

     

     [ fig 23 - 1 ]


[ script 23 - 1 ]
scene = xshade.scene()
scene.exit_modify_mode()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-10000, 0, 0], None, [-5000, 5000, 0], None, None)
scene.append_point([10000, 0, 0], [5000, 5000, 0], None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()		#  中心線
scene.memory()


scene.begin_creating()
scene.create_line(None, [[-12500, 2500, 0], [-10000, 0, 0],[ -7500, -2500, 0]], False)
scene.end_creating()
scene.sweep()
ob = scene.active_shape()
ob2 = ob.dad					#  掃引体
ob2.switch()

scene.active_shapes = [ob1, ob2]
scene.enter_modify_mode()
scene.select_all()

[ script 23 - 2 ]
scene = xshade.scene()
scene.exit_modify_mode()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-12500, -2500, 0], None, [-5000, 5000, 0], None, None)
scene.append_point([15000, 0, -5000], [10000, 5000, 0], None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()		#  中心線
scene.memory()


scene.begin_creating()
scene.create_line(None, [[-15000, 0, 0], [-12500, -2500, 0],[ -10000, -5000, 0]], False)
scene.end_creating()
scene.sweep()
ob = scene.active_shape()
ob2 = ob.dad					#  掃引体
ob2.switch()

scene.active_shapes = [ob1, ob2]
scene.enter_modify_mode()
scene.select_all()

#3

23 - 3  平行曲線


handle 長さの決定に当たり一番理想的なのは、掃引体の外形( = 交差線形状 )が中心線の平行曲線になっていることです。

しかし一般に bezier line の平行曲線を bezier line で記述することはできません。

そこで平行曲線に近い形状を得る方法を考えてみます。


単純な考えとして、bezier control point で形成される折れ線と平行する折れ線を考え、それを求める平行曲線の bezier control point としてみます。

[ script 23 - 3 ] はこの実験 script で、二つの bezier control 間の距離と、bezier 曲線の中点の距離をレポートします。

結果は次のとおりで、良好な曲線を得るには handle 長さをもう少し長くしなければなりません。


          [ fig 23 - 2 ]


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

scene = xshade.scene()

c = vec3(0, -10000, 0)
p = []
p.append([-10000, 0, 0])
p.append([-5000, 5000, 0])
p.append([5000, 5000, 0])
p.append([10000, 0, 0])
p = vec3list(p)

d = 5000
q = []
for i in range(4) :
	v = p[i] - c
	v.norm()
	q.append(p[i] + d*v)
	
scene.begin_creating()
scene.begin_line('1', False)
scene.append_point(p[0], None, p[1], None, None)
scene.append_point(p[3], p[2], None, None, None)
scene.end_line()
scene.end_creating()
bz = labo.get_bezier(xshade, 0)
p1 = labo.bezier_line_position(bz, 0.5)

scene.begin_creating()
scene.begin_line('2', False)
scene.append_point(q[0], None, q[1], None, None)
scene.append_point(q[3], q[2], None, None, None)
scene.end_line()
scene.end_creating()
bz = labo.get_bezier(xshade, 0)
p2 = labo.bezier_line_position(bz, 0.5)

v = p2 - p1
print 'd = ', d, '       中心点間距離 = ', v.abs()

#4

23 - 4  handle 長さ(1)


最初に述べたように、Shade による handle 長さの求め方のロジックは解明できていません。

ただし、handle 先端は同一平面上にあることが解っています。

そこで、下図 ψ = 2αに対する補正角φを実測し、ψとφの table を作成して script の内部データとし、線形補間でψに対するφを求める という方法を採用することにします。

          [ fig 23 - 3 ]


[ script 23 - 4 ] はこのデータを測定する script です。
**測定結果**

               [ table 23 - 1 ]


[ script 23 - 4 ]
from vec3 import *
from math import pi, cos, sin, acos

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


L = 10000
psiL = [15, 30, 60, 90, 120, 126, 130, 150, 165, 170, 175, 177.5]

scene.create_part()
ob = scene.active_shape()

for psi in psiL :
	x = -L*sin(psi/2*pi/180)
	y = -L*cos(psi/2*pi/180)

	scene.begin_creating()
	scene.begin_line(None, False)
	scene.append_point([2*x,2*y, 0], None, [x, y, 0], None, None)
	scene.append_point([-2*x, 2*y, 0], [-x, y, 0], None, None, None)
	scene.end_line()
	scene.end_creating()
	scene.memory()
	ob1 = scene.active_shape()

	scene.begin_creating()
	scene.create_line(None, [[2*x + y/2,2*y - x/2, 0], [2*x,2*y, 0], [2*x - y/2,2*y + x/2, 0]], False)
	scene.end_creating()
	scene.sweep()
	ob2 = scene.active_shape().dad
	ob2.switch()
	ob2.disclosed = False
	ob2.name = str(psi)
	
	ob1.remove()
	ob2.activate()
	
	ob_1 = ob2.son.bro
	ob_2 = ob_1.bro
	
	p1 = vec3(ob_1.control_point(0).position)
	p2 = vec3(ob_2.control_point(0).position)
	
	q1 = vec3(ob_1.control_point(0).out_handle)
	q2 = vec3(ob_2.control_point(0).out_handle)

	print ''

	v = q1 - q2
	v.norm()
	u = vec3(-1, 0, 0)
	
	alpha = psi/2*pi/180
	beta = pi/4 + alpha/2
	phi = acos(u.dot(v)) - beta

	print psi, phi

ob.activate()

#5

23 - 5  handle 長さ(2)


handle 長さの求め方は、bezier control point 座標の位置関係によって下図のように4つのケースに分けられ、**23 - 4** の方式は case 3 に適用されます。

     

     [ fig 23 - 4 ]


#6

23 - 6  handle 長さを定める script(1)


[ script 23 - 5 ] に記載しているのは 関数 **sweep_plane_normal( )** で、handle 長さを求めるのに使用される面法線 **Vp** を返します。
          [ fig 23 - 5 ]
[ script 23 - 5 ]
#  get_phi(psi as float) as float
#
#	psi から線形補間で補正角 phi を返す

def get_phi(psi) :
	from math import pi

	x = [None for i in range(14)]
	y = [None for i in range(14)]
	x[0] = 0.
	y[0] = 0.
	x[1] = pi*15./180
	y[1] = 0.0182718
	x[2] = pi*30./180
	y[2] = 0.0387312
	x[3] = pi*60./180
	y[3] = 0.0741119
	x[4] = pi*90./180
	y[4] = 0.1021102
	x[5] = pi*120./180
	y[5] = 0.1167868
	x[6] = pi*126./180
	y[6] = 0.1172989
	x[7] = pi*130./180
	y[7] = 0.1170475
	x[8] = 150*pi/180
	y[8] = 0.1064218
	x[9] = 165*pi/180
	y[9] = 0.0842835
	x[10] = 170*pi/180
	y[10] = 0.067609
	x[11] = 175*pi/180
	y[11] = 0.051760
	x[12] = 177.5*pi/180
	y[12] = 0.031373
	x[13] = pi
	y[13] = 0.
	
	for i in range(12, -1, -1) :
		if x[i] <= psi :
			return y[i] + (y[i + 1] - y[i])*(psi - x[i])/(x[i + 1] - x[i])
			
			
				
#  sweep_plane_normal(p0 as vec3, p1 as vec3, p2 as vec3) as vec3
#
#	掃引中心線の bezier data bz (p0, p1, p2) から、掃引体交差方向の handle 長さを求めるための面法線を返す
#	基準となる anchor point に handle  が出ていることが前提
#
#		p0, p1, p2 :		outhandle 側を求める場合は		bz[0], bz[1], bz[2]
#							inhandle 側を求める場合は		bz[3], bz[2], bz[1]

def sweep_plane_normal(p0, p1, p2) :
	from math import pi, acos
	
	v1 = p1 - p0					#  v1.abs() != 0 が前提
	v2 = p2 - p1
	v1.norm()
	v2.norm()
	
	if v2.abs2() == 0 :
		return v1
	else :
		a = v1.dot(v2)
		if abs(a) >= 0.99999994 :
			return v1
			
		elif a >= 0 :
			vn = v1 + v2
			axis = v1*v2
			theta = acos(v1.dot(v2))
			psi = pi - 2*theta
			phi = get_phi(psi)			#  psi から線形補間で補正角 phi を返す
			Q = quaternion().rotation(axis, phi)
			vp = Q*vn
			
		else :
			axis = v1*v2
			Q = quaternion().rotation(axis, pi/4)
			vp = Q*v1
			
		vp.norm()	
		return vp

#7

23 - 7  handle 長さを定める script(2)


[ script 23 - 6 ] に記載しているのは 関数 **sweep_handle( )** で handle 座標を返します。
          [ fig 23 - 6 ]
[ script 23 - 6 ] 内の記述
if d < 0 :					#  食い込みが発生する
	d = -d/12

については 23 - 9 < 食い込みが発生するケース > を参照して下さい。


[ script 23 - 6 ]

#  sweep_handle(w as vec3, u as vec3,  Q as vec3, r as float, p as vec3, vp as vec3) as vec3
#
#	handle 座標を返す

def sweep_handle(w, u, Q, r, p, vp) :
	#     L :  点 w を通り、u 方向に進む直線
	#	PL0 : 点 Q を通り面法線 u なる面
	#	PL1 : 点 p を通り面法線 vp なる面
	#	精度確保のため r = 1 とはせず、r = bounding box size / 16 としてある

	a = u.dot(w - Q)			#  点 w と面 PL0 との符号付き距離		
	d1 = vp.dot(w - p)			#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*u - p)	#  点 w + r*u と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)			#  点 w と、直線 L と 面 PL1 との交点までの 符号付き距離
	d = d + a					#  handle の符号つき長さ
	if d < 0 :					#  食い込みが発生する
		d = -d/12
	return w + d*u

#8

23 - 8  Script


以上より、handle 長さをフォローした Shade 掃引シミュレーション script を [ script 23 - 7 ] に記します。
[ script 23 - 7 ]
import labo
from matrix import *
from quaternion import *

		

#  sweep_section_quaternion(bz as vec3 matrix ,atrix) as quaternion
#
#	bezier data bz から掃引時の断面形状の姿勢を与える quaternion を返す

def sweep_section_quaternion(bz) :
	from math import acos
	
	v1 = labo.bezier_line_tangent(bz, 0)		#  outhandle 側接線ベクトル
	v2 = labo.bezier_line_tangent(bz, 1)		#  inhandle 側接線ベクトル
	vn = bz[3] - bz[0]							#   始端と終端を結ぶ vector
	u1 = vn*v1
	u2 = vn*v2
	vn.norm()
	u1.norm()
	u2.norm()
	cosA = u1.dot(u2)							#  4つの bezier control point の平坦性を表す角度の cos値						
	cosB = v1.dot(v2)							#  両端の接線ベクトルの交差角の cos値
	
	#  flat : 4 つの bezier control point の平坦性 flag
	if (u1.abs2() == 0) or (u2.abs2() == 0)  :
		flat = True
	elif abs(cosA) >= 0.98  :
		flat = True
	else :
		flat = False

	# pi_rotaion :  両端の接線ベクトルの回転角が180度に近い
	if (v1.abs2() == 0) and (v2.abs2() == 0) :	#  handle の出ていない重点						
		pi_rotation = False
	elif cosB >= -0.9 :
		pi_rotation = False
	else :
		pi_rotation = True
		

	if flat :									#  4 つの bezier control point  が平坦
		if not pi_rotation :					#  両端の接線ベクトルの回転角が180度に近くはない
			if (v1.abs2() == 0) and (v2.abs2() == 0) :	#  handle の出ていない重点
				return quaternion()
			else :
				if v1.dot(v2) >= 0.99999994 :
					return quaternion()
				else :
					return quaternion().slerp(v1, v2)
		
		else :								#  両端の接線ベクトルの回転角が180度に近い
			if vn.abs2() == 0 :				#  重点
				return quaternion()
					
			elif abs(vn.dot(v1)) >= 0.99999994 or abs(vn.dot(v2)) >= 0.99999994 :		#  handle が point 間に平行
				if v1.dot(v2) > -0.99999994 :
					return quaternion().slerp(v1, v2)
				else :
					return quaternion()		#  この特殊ケースのみ Shade の断面配置を再現できない
					
			else :
				v = v2 - v1 
				u = u1 + u2
				axis = u*v
				if axis.abs2() <1e-24 :
					axis = v*vn
                
				phi = acos(u1.dot(-u2))/2
				if (u1*u2).dot(vn) < 0 :
					phi = -phi
				Q = quaternion().rotation(v, phi)			
				axis = Q*axis
				axis.norm()					#  掃引断面の quaternion 回転の回転軸	
				
				w1 = axis*v1
				w2 = axis*v2

				return quaternion().slerp(w1, w2)
			
	
	else :									#  4 つの bezier control point  が平坦でない
		t1 = 0
		v1 = labo.bezier_line_tangent(bz, t1)
		Q = quaternion()
		for i in range(16) :					#  bezier parameter を16等分して細かく回転を16回繰り返す
			t2 = float(i + 1)/16
			v2 = labo.bezier_line_tangent(bz, t2)
			Q = quaternion().slerp(v1, v2)*Q
			t1 = t2
			v1 = v2
	
		return Q				


		
#  get_phi(psi as float) as float
#
#	psi から線形補間で補正角 phi を返す

def get_phi(psi) :
	from math import pi

	x = [None for i in range(14)]
	y = [None for i in range(14)]
	x[0] = 0.
	y[0] = 0.
	x[1] = pi*15./180
	y[1] = 0.0182718
	x[2] = pi*30./180
	y[2] = 0.0387312
	x[3] = pi*60./180
	y[3] = 0.0741119
	x[4] = pi*90./180
	y[4] = 0.1021102
	x[5] = pi*120./180
	y[5] = 0.1167868
	x[6] = pi*126./180
	y[6] = 0.1172989
	x[7] = pi*130./180
	y[7] = 0.1170475
	x[8] = 150*pi/180
	y[8] = 0.1064218
	x[9] = 165*pi/180
	y[9] = 0.0842835
	x[10] = 170*pi/180
	y[10] = 0.067609
	x[11] = 175*pi/180
	y[11] = 0.051760
	x[12] = 177.5*pi/180
	y[12] = 0.031373
	x[13] = pi
	y[13] = 0.
	
	for i in range(12, -1, -1) :
		if x[i] <= psi :
			return y[i] + (y[i + 1] - y[i])*(psi - x[i])/(x[i + 1] - x[i])
			
			
				
#  sweep_plane_normal(p0 as vec3, p1 as vec3, p2 as vec3) as vec3
#
#	掃引中心線の bezier data bz (p0, p1, p2) から、掃引体交差方向の handle 長さを求めるための面法線を返す
#	基準となる anchor point に handle  が出ていることが前提
#
#		p0, p1, p2 :		outhandle 側を求める場合は		bz[0], bz[1], bz[2]
#							inhandle 側を求める場合は		bz[3], bz[2], bz[1]

def sweep_plane_normal(p0, p1, p2) :
	from math import pi, acos
	
	v1 = p1 - p0					#  v1.abs() != 0 が前提
	v2 = p2 - p1
	v1.norm()
	v2.norm()
	
	if v2.abs2() == 0 :
		return v1
	else :
		a = v1.dot(v2)
		if abs(a) >= 0.99999994 :
			return v1
			
		elif a >= 0 :
			vn = v1 + v2
			axis = v1*v2
			theta = acos(v1.dot(v2))
			psi = pi - 2*theta
			phi = get_phi(psi)			#  psi から線形補間で補正角 phi を返す
			Q = quaternion().rotation(axis, phi)
			vp = Q*vn
			
		else :
			axis = v1*v2
			Q = quaternion().rotation(axis, pi/4)
			vp = Q*v1
			
		vp.norm()	
		return vp
	
	
	
#  sweep_handle(w as vec3, u as vec3,  Q as vec3, r as float, p as vec3, vp as vec3) as vec3
#
#	handle 座標を返す

def sweep_handle(w, u, Q, r, p, vp) :
	#     L :  点 w を通り、u 方向に進む直線
	#	PL0 : 点 Q を通り面法線 u なる面
	#	PL1 : 点 p を通り面法線 vp なる面
	#	精度確保のため r = 1 とはせず、r = bounding box size / 16 としてある

	a = u.dot(w - Q)			#  点 w と面 PL0 との符号付き距離		
	d1 = vp.dot(w - p)			#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*u - p)	#  点 w + r*u と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)			#  点 w と、直線 L と 面 PL1 との交点までの 符号付き距離
	d = d + a					#  handle の符号つき長さ
	if d < 0 :					#  食い込みが発生する
		d = -d/12
	return w + d*u

	


#  sweep(ob1 as shape, ob2 as shape) as shape
#
#	掃引体を Shade 上に作成し、その shape を返す
#		ob1 :	掃引中心線形状
#		ob2 :	掃引断面形状	

def sweep(ob1, ob2) :
	if (ob1 == None) or (ob2 == None) :
		return
	if (not isinstance(ob1, xshade.line)) or (not isinstance(ob2, xshade.line)) :
		return

	#  掃引中心線データ取得 nop, closed, bZ
	ob1.activate()
	nop = ob1.number_of_control_points		#  ポイント数
	if nop < 2 :
		return
	closed = ob1.closed						#  開閉情報
	bZ = labo.get_bezier(xshade, -1)		#  全区間の bezier data [matrix, matrix, … ]
	
	
	# mx1, mx2, mx22
	mx1 = matrix(ob2.world_to_local_matrix)
	mx2 = matrix(ob2.local_to_world_matrix)
	if not closed :
		mx22 = matrix(ob1.local_to_world_matrix)
		
		
	#  掃引中心線 の handle 情報取得
	has_in_handle = []			#  掃引中心線の inhandle の有無のリスト
	has_out_handle = []			#  掃引中心線の outhandle の有無のリスト
	linked = []					#  掃引中心線の hanndle link リスト
	for i in range(nop) :
		has_in_handle.append(ob1.control_point(i).has_in_handle)
		has_out_handle.append(ob1.control_point(i).has_out_handle)
		linked.append(ob1.control_point(i).linked)

	
	#  ob0 : 掃引体を格納する自由曲面
	ob2.activate()
	scene.create_surface_part(None)
	ob0 = scene.active_shape()				#   掃引体を格納する自由曲面
	ob0.surface_closed = closed
	ob2.place_child(1)

	
	#  掃引断面を配置
	p1 = bZ[0][0]
	Mt1 = matrix().translate(-p1[0], -p1[1], -p1[2])
	Q1 = quaternion()
	n = nop - 1
		
	for i in range(n) :
		p2 = bZ[i][3]
		v2 = labo.bezier_line_tangent(bZ[i], 1)
		Mt2 = matrix().translate(p2[0], p2[1], p2[2])
		Q2 = sweep_section_quaternion(bZ[i])
		Q2 = Q2*Q1
		Mr = Q2.matrix()
		M = mx2*Mt1*Mr*Mt2*mx1
	
		ob2.copy_object(M)
		ob = ob2.bro
		ob.place_brother(i)
	
		Q1 = Q2

		
	#  掃引体の交差方向 handle をセット
	bb = labo.vec3_bounding_box_size(bZ)
	r = bb[3]/16												#  sweep_handle( ) の引数に使用
	
	n = ob2.number_of_control_points
	ob0.switch()
	ob = ob0.son
	for i in range(n) :
		ob = ob.bro												#  掃引体内部の交差方向線形状
		
		for j in range(nop) :
			w = vec3(ob.control_point(j).position)*mx2			#  交差方向線形状の point 座標

			if not has_in_handle[j] :
				inH = w											#  inhandle 座標
			else :
				if (j == 0) and not closed :
					p = vec3(ob1.control_point(0).in_handle)*mx22
					inH = w + p - bZ[0][0]						#  inhandle 座標
				else :
					if j == 0 :									#   中心線が閉じている場合の始端
						k = nop - 1
					else :
						k = j - 1
					vp = sweep_plane_normal(bZ[k][3], bZ[k][2], bZ[k][1])
					u = bZ[k][2] - bZ[k][3] 
					u.norm()
					p = bZ[k][2]
					Q = bZ[k][3]
					inH = sweep_handle(w, u, Q, r, p, vp)		#  inhandle 座標

		
			if not has_out_handle[j] :
				outH = w										#  outhandle 座標
			else :	
				if (j == nop - 1) and not closed :
					p = vec3(ob1.control_point(nop - 1).out_handle)*mx22
					outH = w + p - bZ[nop - 2][3]				#  outhandle 座標
				else :
					vp = sweep_plane_normal(bZ[j][0], bZ[j][1], bZ[j][2])
					u = bZ[j][1] - bZ[j][0] 
					u.norm()
					p = bZ[j][1]
					Q = bZ[j][0]
					outH = sweep_handle(w, u, Q, r, p, vp)		#  outhandle 座標	
									
					
			ob.control_point(j).in_handle = inH*mx1
			ob.control_point(j).out_handle = outH*mx1				
			ob.control_point(j).linked = linked[j]


	ob0.switch()
	ob0.activate()
	ob0.disclosed = False
	
	return ob0
	

	
	
	
#  Shade  上で 掃引中心線 , 掃引断面 の順で選択して実行

scene = xshade.scene()

scene.exit_modify_mode()
[ob1, ob2] = scene.active_shapes

#  Shade original sweep
ob1.activate()
scene.memory()
ob2.activate()
ob2.copy_object(None)
ob2.bro.activate()
scene.sweep()
ob3 = scene.active_shape().dad
ob3.activate()
ob3.name = 'Shade sweep'
ob3.disclosed = False


#  simulated sweep
ob2.copy_object(None)
ob4 = ob2.bro
ob5 = sweep(ob1, ob4)
ob5.name = 'simulated'

scene.active_shapes = [ob3, ob5]

#9

23 - 9  handle 長さにおける留意点


Shade 掃引による handle 長さに関していくつかの留意点を記します。
**< 補正角 φ >**
**23 - 4** で触れたように補正角φの求め方は解明できていませんが、中心線が 1/4 円であっても、中点における距離はやや短くなっていて、掃引体はややひしゃげた形をなしていいます。
[ script 23 - 8 ] はこの検証 script です。

Custom 掃引 で、合理的な補正角φの求め方を提案します。


検証結果

  • L = 5000
  • 中点間距離 = 4966.31527712

          [ fig 23 - 7 ]


[ script 23 - 8 ]
import labo

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

r = 10000
scene.begin_creating()
scene.create_disk(None, [0, 0, 0],r, 2)
scene.end_creating()
scene.convert_to_line_object()
ob1 = scene.active_shape()		#  中心線
ob1.closed = False
ob1.remove_control_point(3)
ob1.remove_control_point(2)
scene.memory()


scene.begin_creating()
scene.create_line(None, [[0, -1.5*r, 0], [0, -r, 0,]], False)
scene.end_creating()
scene.sweep()
ob = scene.active_shape()
ob2 = ob.dad					#  掃引体
ob2.switch()

ob = ob2.son.bro
ob.activate()
bz1 = labo.get_bezier(xshade, 0)
ob = ob.bro
ob.activate()
bz2 = labo.get_bezier(xshade, 0)

ob2.activate()

v = labo.bezier_line_position(bz1, 0.5) - labo.bezier_line_position(bz2, 0.5)

print 'L = ', r/2, '     中点間距離 = ', v.abs()


**< handle 座標が一致するケース >**
[ fig 23 - 4 ] の Case 2 で示したように、両端から伸びる handle 先端の座標が一致する場合、基準面法線は特別な設定がなされています。

したがって、 handle 長さが少しでも異なると、得られる掃引断面には大きな変化が生じます。


[ script 23 - 9 ] はこの検証 script です。

Custom 掃引 で改善策を提案します。

          [ fig 23 - 8 ]


[ script 23 - 9 ]
scene = xshade.scene()
scene.exit_modify_mode()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-10000, -10000, 0], None, [-10000, 4000, 0], None, None)
scene.append_point([10000, 10000, 0], [-10000, 4000, 0], None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()		#  中心線
scene.memory()


scene.begin_creating()
scene.create_line(None, [[-15000, -10000, 0], [-5000, -10000, 0]], False)
scene.end_creating()
scene.sweep()
ob = scene.active_shape()
ob2 = ob.dad					#  掃引体
ob2.switch()


scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-10000, -10000, 0], None, [-10000, 3900, 0], None, None)
scene.append_point([10000, 10000, 0], [-10000, 4000, 0], None, None, None)
scene.end_line()
scene.end_creating()
ob3 = scene.active_shape()		#  中心線
scene.memory()


scene.begin_creating()
scene.create_line(None, [[-15000, -10000, 0], [-5000, -10000, 0]], False)
scene.end_creating()
scene.sweep()
ob = scene.active_shape()
ob4 = ob.dad					#  掃引体
ob4.switch()

scene.active_shapes = [ob2, ob4]


**< 食い込みが発生するケース >**
中心線のポイント密度や曲がりに対して大きなサイズの断面を掃引すると、掃引体に食い込みが発生する場合があります。

[ script 23 - 10 ] でサンプル形状を出力し、シミュレーション script [ script 23 - 7 ] を実行します。


食い込み部分の handle は [ fig 23 - 6 ] で得られる長さより 短く なり、さらに、その方向が 反転 しています。

handle 長さを短くした方がいいということは感覚的に理解できますが、Shade がどのような基準で短くしているかは解明できていません。

シミュレーション script では一律に 1/12 としていますが、Shade では一律ではなくもう少し短くなるケースもあります。

一方 handle 方向を反転させているのは、その方が幾分かでもレンダリング結果がよくなるということかもしれません。

          [ fig 23 - 9 ]

          [ fig 23 - 10 ]

          [ fig 23 - 11 ]


[ script 23 - 10 ]
scene = xshade.scene()
scene.exit_modify_mode()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-7500, 0, 0], None, [-7500, 20000, 0], None, None)
scene.append_point([7500, 0, 0], [0, 20000, 0], None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()		#  中心線
ob1.insert_control_point(0.7)
ob1.insert_control_point(0.475/0.7)
ob1.insert_control_point(0.25/0.475)


scene.begin_creating()
scene.create_disk(None, [-7500, 0, 0], 5500, 1)
scene.end_creating()
scene.convert_to_line_object()
ob2 = scene.active_shape()
ob2.reverse()

scene.active_shapes = [ob1, ob2]

#10

勉強になります。
時間があるときに読み返したいと思います。


#11

ベジェ曲線の曲げが大きいとオフセット曲線は一つのセグメントでは表現できなくなるのでしょうか。


#12

オフセット曲線 = 平行曲線 という意味での御質問かと思います。

一言で言ってしまえば「できません」という身も蓋もない回答になってしまいますが、これはあくまでも幾何的に厳密な定義に基づいてのことでして、そうであっても感覚的に許容できる範囲はあると思います。

それは個人的な美意識や目的によって左右もされるでしょうから、そのような見方を否定するものではありません。

Shade Labo での基本スタンスは幾何的にかなり厳密な見方をベースとしていますので、特に 3D 関連の参考図書での記述と異なる所もあるかと思います。

でもそれはスタンスの違いによるものであって、正しい、間違っている、ということではないと思っています。

tech_Wandervogel さんの御質問の趣旨からは少し逸脱してしまったようですが、よい機会でしたので一言述べさせて頂きました。


#13

やはり曲げの大きさによっては線分の分割が起きるわけですね。

以前、私の方でもオフセット曲線をトライしました。
解析的な手法でオフセット曲線を求める内容で、あまりスマートな方法ではないということで日の目を見ることはありませんでした。

他にチューブ形状作成について、クォータニオンで軌道を作ることを想定していたので勉強させて頂きます。